--!nolint LocalShadow
--!optimize 2

local Prefix = require("../../Prefix")
local parsePrefixWindows = require("@self/parsePrefix")

type Prefix = Prefix.Prefix

local MAIN_SEPARATOR_STR = if _G.SYS_PATH_SEPARATOR == "\\" or _G.SYS_PATH_SEPARATOR == "/"
	then _G.SYS_PATH_SEPARATOR
	elseif (_G.LUA_ENV == "lune" or string.lower(string.sub(tostring(_VERSION), 1, 4)) == "lune") and (require :: any)(
		"@lune/process"
	).os == "windows" then "\\"
	else "/"

local IS_WINDOWS = MAIN_SEPARATOR_STR == "\\"

local function isSepByteUnix(b: string): boolean
	return b == "/"
end

local function isSepByteWindows(b: string): boolean
	return b == "/" or b == "\\"
end

local function isVerbatimSepUnix(b: string): boolean
	return b == "/"
end

local function isVerbatimSepWindows(b: string): boolean
	return b == "\\"
end

local isSepByte: (string) -> boolean = if IS_WINDOWS then isSepByteWindows else isSepByteUnix
local isVerbatimSep: (string) -> boolean = if IS_WINDOWS then isVerbatimSepWindows else isVerbatimSepUnix

local function parsePrefixUnix(_path: string): Prefix?
	return nil
end

local parsePrefix: (path: string) -> Prefix? = if IS_WINDOWS then parsePrefixWindows else parsePrefixUnix

return table.freeze({
	MAIN_SEPARATOR_STR = MAIN_SEPARATOR_STR;
	isSepByte = isSepByte;
	isVerbatimSep = isVerbatimSep;
	parsePrefix = parsePrefix;
})


--!optimize 2
--!strict

local AssertionErrorModule = require("@self/AssertionErrorGlobal")
export type AssertionError = AssertionErrorModule.AssertionError
return AssertionErrorModule.AssertionError


--!optimize 2
--!strict

return table.freeze({
	toJSBoolean = require("@self/toJSBoolean");
})


--!optimize 2
--!strict

local Number = require("../Number")

-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
return function(value: any): boolean
	return not not value and value ~= 0 and value ~= "" and not Number.isNaN(value)
end


--!optimize 2
--!strict

local isArray = require("./isArray")
local types = require("@packages/ES7Types")
local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>

local RECEIVED_OBJECT_ERROR = "Array.concat(...) only works with array-like tables but "
	.. "it received an object-like table.\nYou can avoid this error by wrapping the "
	.. "object-like table into an array. Example: `concat({1, 2}, {a = true})` should "
	.. "be `concat({1, 2}, { {a = true} }`"

-- capture two separate generic arguments so that the type error in abuse cases is actionable, but needs CLI-49876 to avoid a false negative
local function concat<T, S>(source: Array<T> | T, ...: Array<S> | S): Array<T> & Array<S>
	local array
	local elementCount = 0

	if isArray(source) then
		array = table.clone(source :: Array<T>)
		elementCount = #(source :: Array<T>)
	else
		elementCount += 1
		array = {}
		array[elementCount] = source :: T
	end

	for i = 1, select("#", ...) do
		local value = select(i, ...)
		if value == nil then
			-- do not insert nil
			continue
		end

		if type(value) == "table" then
			-- deviation: assume that table is an array, to avoid the expensive
			-- `isArray` check. In DEV mode, we throw if it is given an object-like
			-- table.
			if __DEV__ and not isArray(value) then
				error(RECEIVED_OBJECT_ERROR)
			end

			for k = 1, #value do
				elementCount += 1
				array[elementCount] = value[k]
			end
		else
			elementCount += 1
			array[elementCount] = value
		end
	end

	return (array :: any) :: Array<T> & Array<S>
end

return concat


--!optimize 2
--!strict

local types = require("@packages/ES7Types")

local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>
type Object = types.Object
type callbackFn<T> = (element: T, index: number, array: Array<T>) -> boolean
type callbackFnWithThisArg<T, U> = (self: U, element: T, index: number, array: Array<T>) -> boolean

-- Implements Javascript's `Array.prototype.every` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
return function<T, U>(t: Array<T>, callback: callbackFn<T> | callbackFnWithThisArg<T, U>, thisArg: U?): boolean
	if __DEV__ then
		if type(t) ~= "table" then
			error(string.format("Array.every called on %s", typeof(t)))
		end

		if type(callback) ~= "function" then
			error("callback is not a function")
		end
	end

	local len = #t
	local k = 1

	while k <= len do
		local kValue = t[k]
		local testResult

		if kValue ~= nil then
			if thisArg ~= nil then
				testResult = (callback :: callbackFnWithThisArg<T, U>)(thisArg, kValue, k, t)
			else
				testResult = (callback :: callbackFn<T>)(kValue, k, t)
			end

			if not testResult then
				return false
			end
		end

		k += 1
	end

	return true
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>
type Object = types.Object
type callbackFn<T> = (element: T, index: number, array: Array<T>) -> boolean
type callbackFnWithThisArg<T, U> = (thisArg: U, element: T, index: number, array: Array<T>) -> boolean

-- Implements Javascript's `Array.prototype.filter` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
return function<T, U>(t: Array<T>, callback: callbackFn<T> | callbackFnWithThisArg<T, U>, thisArg: U?): Array<T>
	if __DEV__ then
		if type(t) ~= "table" then
			error(string.format("Array.filter called on %s", typeof(t)))
		end

		if type(callback) ~= "function" then
			error("callback is not a function")
		end
	end

	local len = #t
	local res = {}
	local index = 1

	if thisArg == nil then
		for i = 1, len do
			local kValue = t[i]
			if kValue ~= nil then
				if (callback :: callbackFn<T>)(kValue, i, t) then
					res[index] = kValue
					index += 1
				end
			end
		end
	else
		for i = 1, len do
			local kValue = t[i]
			if kValue ~= nil then
				if (callback :: callbackFnWithThisArg<T, U>)(thisArg, kValue, i, t) then
					res[index] = kValue
					index += 1
				end
			end
		end
	end

	return res
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>
type PredicateFunction<T> = (value: T, index: number, array: Array<T>) -> boolean

return function<T>(array: Array<T>, predicate: PredicateFunction<T>): T?
	-- for i = 1, #array do
	-- 	local element = array[i]
	-- 	if predicate(element, i, array) then
	-- 		return element
	-- 	end
	-- end

	for i, element in array do
		if predicate(element, i, array) then
			return element
		end
	end

	return nil
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>
type PredicateFunction<T> = (T, number, Array<T>) -> boolean

return function<T>(array: Array<T>, predicate: PredicateFunction<T>): number
	-- for i = 1, #array do
	-- 	local element = array[i]
	-- 	if predicate(element, i, array) then
	-- 		return i
	-- 	end
	-- end

	for i, element in array do
		if predicate(element, i, array) then
			return i
		end
	end

	return -1
end


--!optimize 2
--!strict

local isArray = require("./isArray")
local types = require("@packages/ES7Types")

local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>

local function flat<T>(array: Array<T>, depth_: number?): Array<T>
	if __DEV__ then
		if type(array) ~= "table" then
			error(string.format("Array.flat called on %s", typeof(array)))
		end

		if depth_ ~= nil and type(depth_) ~= "number" then
			error("depth is not a number or nil")
		end
	end

	local depth = depth_ or 1
	local newArray = {}

	for _, v in array do
		if isArray(v) then
			local vArray = (v :: any) :: Array<T>
			local innerArrFlat: Array<T> = if depth > 1 then flat(vArray, depth - 1) else vArray
			for _, innerValue in innerArrFlat do
				table.insert(newArray, innerValue)
			end
		else
			table.insert(newArray, v)
		end
	end

	return newArray
end

return flat


--!optimize 2
--!strict

local flat = require("./flat")
local map = require("./map")
local types = require("@packages/ES7Types")

local __DEV__ = _G.__DEV__

type Array<T> = types.Array<T>
type callbackFn<T, U> = (element: T, index: number, array: Array<T>) -> U
type callbackFnWithThisArg<T, U, V> = (thisArg: V, element: T, index: number, array: Array<T>) -> U

local function flatMap<T, U, V>(
	array: Array<T>,
	callback: callbackFn<T, U> | callbackFnWithThisArg<T, U, V>,
	thisArg: V?
): Array<U>
	if __DEV__ then
		if type(array) ~= "table" then
			error(string.format("Array.flatMap called on %s", typeof(array)))
		end

		if type(callback) ~= "function" then
			error("callback is not a function")
		end
	end

	return flat(map(array, callback, thisArg))
end

return flatMap


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>
type Object = types.Object
type callbackFn<T> = (element: T, index: number, array: Array<T>) -> ()
type callbackFnWithThisArg<T, U> = (thisArg: U, element: T, index: number, array: Array<T>) -> ()

-- Implements Javascript's `Array.prototype.forEach` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
return function<T, U>(t: Array<T>, callback: callbackFn<T> | callbackFnWithThisArg<T, U>, thisArg: U?): ()
	if __DEV__ then
		if type(t) ~= "table" then
			error(string.format("Array.forEach called on %s", typeof(t)))
		end

		if type(callback) ~= "function" then
			error("callback is not a function")
		end
	end

	local len = #t
	local k = 1

	while k <= len do
		local kValue = t[k]

		if thisArg ~= nil then
			(callback :: callbackFnWithThisArg<T, U>)(thisArg, kValue, k, t)
		else
			(callback :: callbackFn<T>)(kValue, k, t)
		end

		if #t < len then
			-- don't iterate on removed items, don't iterate more than original length
			len = #t
		end

		k += 1
	end
end


--!optimize 2
--!strict

local ES7Types = require("@packages/ES7Types")
local InstanceOf = require("@packages/InstanceOf")

local Map = require("../Map/Map")
local Set = require("../Set")
local fromString = require("./fromString")
local isArray = require("./isArray")

type Array<T> = ES7Types.Array<T>
type Map<K, V> = ES7Types.Map<K, V>
type Object = ES7Types.Object
type Set<T> = ES7Types.Set<T>

type FromArrayFunction<T, U> = (element: T, index: number) -> U
type FromArrayThisFunction<T, U> = (thisArg: any, element: T, index: number) -> U
local function fromArray<T, U>(
	value: Array<T>,
	callback: FromArrayFunction<T, U> | FromArrayThisFunction<T, U> | nil,
	thisArg: Object?
): Array<U> | Array<T> | Array<string>
	local array = {}

	if callback then
		local arrayLength = #(value :: Array<T>)
		array = table.create(arrayLength)
		for i = 1, arrayLength do
			if thisArg ~= nil then
				(array :: Array<U>)[i] = (callback :: FromArrayThisFunction<T, U>)(thisArg, (value :: Array<T>)[i], i)
			else
				(array :: Array<U>)[i] = (callback :: FromArrayFunction<T, U>)((value :: Array<T>)[i], i)
			end
		end
	else
		array = table.clone(value :: Array<T>)
	end

	return array
end

type FromMapFunction<T, U> = (element: T, index: number) -> U
type FromMapThisFunction<T, U> = (thisArg: any, element: T, index: number) -> U
local function fromMap<T, U>(
	value: Map<any, any>,
	callback: nil | FromMapFunction<T, U> | FromMapThisFunction<T, U>,
	thisArg: Object?
	-- FIXME Luau: need overloading so the return type on this is more sane and doesn't require manual casts
): Array<U> | Array<T> | Array<string>
	local array = {}

	if callback then
		array = {}
		for i, v in value :: any do
			if thisArg ~= nil then
				(array :: Array<U>)[i] = (callback :: FromMapThisFunction<T, U>)(thisArg, v, i)
			else
				(array :: Array<U>)[i] = (callback :: FromMapFunction<T, U>)(v, i)
			end
		end
	else
		array = {}
		for i, v in value :: any do
			(array :: Array<T>)[i] = v
		end
	end

	return array
end

type FromSetFunction<T, U> = (element: T, index: number) -> U
type FromSetThisFunction<T, U> = (thisArg: any, element: T, index: number) -> U
local function fromSet<T, U>(
	value: Set<T>,
	callback: nil | FromSetFunction<T, U> | FromSetThisFunction<T, U>,
	thisArg: Object?
	-- FIXME Luau: need overloading so the return type on this is more sane and doesn't require manual casts
): Array<U> | Array<T> | Array<string>
	local array = {}

	if callback then
		array = {}
		for i, v in value :: any do
			if thisArg ~= nil then
				(array :: Array<U>)[i] = (callback :: FromSetThisFunction<T, U>)(thisArg, v, i)
			else
				(array :: Array<U>)[i] = (callback :: FromSetFunction<T, U>)(v, i)
			end
		end
	else
		array = table.clone((value :: any)._array)
	end

	return array
end

type Function<T, U> = (element: T, index: number) -> U
type ThisFunction<T, U> = (thisArg: any, element: T, index: number) -> U

local function From<T, U>(
	value: string | Array<T> | Set<T> | Map<any, any>,
	mapFn: (Function<T, U> | ThisFunction<T, U>)?,
	thisArg: Object?
	-- FIXME Luau: need overloading so the return type on this is more sane and doesn't require manual casts
): Array<U> | Array<T> | Array<string>
	if value == nil then
		error("cannot create array from a nil value")
	end

	local valueType = type(value)

	local array: Array<U> | Array<T> | Array<string>

	if valueType == "string" then
		array = fromString(value :: string, mapFn, thisArg)
	elseif valueType == "table" and isArray(value) then
		array = fromArray(value :: Array<T>, mapFn, thisArg)
	elseif InstanceOf(value, Set) then
		array = fromSet(value :: Set<T>, mapFn, thisArg)
	elseif InstanceOf(value, Map) then
		array = fromMap(value :: Map<any, any>, mapFn, thisArg)
	else
		array = {}
	end

	return array
end

return From


--!optimize 2
--!strict

local ES7Types = require("@packages/ES7Types")
type Object = ES7Types.Object

type FromStringFunction<T, U> = (element: T, index: number) -> U
type FromStringThisFunction<T, U> = (thisArg: any, element: T, index: number) -> U

local function FromString<T, U>(
	value: string,
	callback: FromStringFunction<T, U> | FromStringThisFunction<T, U> | nil,
	thisArg: Object?
): {U} | {T} | {string}
	local valueStringLength = #value
	if callback then
		local array = table.create(valueStringLength)
		for index = 1, valueStringLength do
			if thisArg ~= nil then
				array[index] = (callback :: FromStringThisFunction<T, U>)(
					thisArg,
					string.sub(value, index, index) :: any,
					index
				)
			else
				array[index] = (callback :: FromStringFunction<T, U>)(string.sub(value, index, index) :: any, index)
			end
		end
		return array
	end

	local array = table.create(valueStringLength)
	for index = 1, valueStringLength do
		array[index] = string.sub(value, index, index)
	end
	return array
end

return FromString


--!optimize 2
--!strict

local indexOf = require("./indexOf")
local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>

return function<T>(array: Array<T>, searchElement: T, fromIndex: number?): boolean
	return indexOf(array, searchElement, fromIndex) ~= -1
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>

-- Implements equivalent functionality to JavaScript's `array.indexOf`,
-- implementing the interface and behaviors defined at:
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
--
-- This implementation is loosely based on the one described in the polyfill
-- source in the above link
return function<T>(array: Array<T>, searchElement: T, fromIndex: number?): number
	local fromIndex_ = fromIndex or 1
	local length = #array

	-- In the JS impl, a negative fromIndex means we should use length - index;
	-- with Lua, of course, this means that 0 is still valid, but refers to the
	-- end of the array the way that '-1' would in JS
	if fromIndex_ < 1 then
		fromIndex_ = math.max(length - math.abs(fromIndex_), 1)
	end

	for i = fromIndex_, length do
		if array[i] == searchElement then
			return i
		end
	end

	return -1
end


--!optimize 2
--!strict

export type Array<T> = {T}

return table.freeze({
	concat = require("@self/concat");
	every = require("@self/every");
	filter = require("@self/filter");
	find = require("@self/find");
	findIndex = require("@self/findIndex");
	flat = require("@self/flat");
	flatMap = require("@self/flatMap");
	forEach = require("@self/forEach");
	from = require("@self/from");
	includes = require("@self/includes");
	indexOf = require("@self/indexOf");
	isArray = require("@self/isArray");
	join = require("@self/join");
	map = require("@self/map");
	reduce = require("@self/reduce");
	reverse = require("@self/reverse");
	shift = require("@self/shift");
	slice = require("@self/slice");
	some = require("@self/some");
	sort = require("@self/sort");
	splice = require("@self/splice");
	unshift = require("@self/unshift");
})


--!optimize 2
--!strict

return function(value: any): boolean
	if type(value) ~= "table" then
		return false
	end

	if next(value) == nil then
		-- an empty table is an empty array
		return true
	end

	local length = #value

	if length == 0 then
		return false
	end

	local count = 0
	local sum = 0
	for key in next, value do
		if type(key) ~= "number" then
			return false
		end

		if key % 1 ~= 0 or key < 1 then
			return false
		end

		count += 1
		sum += key
	end

	return sum == count * (count + 1) / 2
end


--!optimize 2
--!strict

local map = require("./map")
type Array<T> = {T}

return function<T>(arr: {T}, separator: string?): string
	if #arr == 0 then
		return ""
	end

	-- JS does tostring conversion implicitely but in Lua we need to do that explicitely
	return table.concat(map(arr, tostring), separator or ",")
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>
type Object = types.Object
type callbackFn<T, U> = (element: T, index: number, array: Array<T>) -> U
type callbackFnWithThisArg<T, U, V> = (thisArg: V, element: T, index: number, array: Array<T>) -> U

-- Implements Javascript's `Array.prototype.map` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
return function<T, U, V>(t: Array<T>, callback: callbackFn<T, U> | callbackFnWithThisArg<T, U, V>, thisArg: V?): Array<U>
	if __DEV__ then
		if type(t) ~= "table" then
			error(string.format("Array.map called on %s", typeof(t)))
		end

		if type(callback) ~= "function" then
			error("callback is not a function")
		end
	end

	local len = #t
	local A = {}
	local k = 1

	while k <= len do
		local kValue = t[k]

		if kValue ~= nil then
			local mappedValue

			if thisArg ~= nil then
				mappedValue = (callback :: callbackFnWithThisArg<T, U, V>)(thisArg, kValue, k, t)
			else
				mappedValue = (callback :: callbackFn<T, U>)(kValue, k, t)
			end

			A[k] = mappedValue
		end

		k += 1
	end

	return A
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>
type reduceFn<T, U> = (previousValue: U, currentValue: T, currentIndex: number, array: Array<T>) -> U

-- Implements Javascript's `Array.prototype.reduce` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
-- TODO Luau: when Luau supports overloads, use them here so that reduceFn can correctly use T when initialValue (U) isn't supplied
return function<T, U>(array: Array<T>, callback: reduceFn<T, U>, initialValue: U?): U
	if __DEV__ then
		if type(array) ~= "table" then
			error(string.format("Array.reduce called on %s", typeof(array)))
		end

		if type(callback) ~= "function" then
			error("callback is not a function")
		end
	end

	local length = #array

	local value: T | U
	local initial = 1

	if initialValue ~= nil then
		value = initialValue
	else
		initial = 2
		if length == 0 then
			error("reduce of empty array with no initial value")
		end

		value = array[1]
	end

	for i = initial, length do
		value = callback(value :: U, array[i], i, array)
	end

	return value :: U
end


--!optimize 2
--!strict
-- https://programming-idioms.org/idiom/19/reverse-a-list/1314/lua
local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>

return function<T>(t: Array<T>): Array<T>
	local n = #t
	local i = 1
	while i < n do
		t[i], t[n] = t[n], t[i]
		i += 1
		n -= 1
	end

	return t
end


--!optimize 2
--!strict

local isArray = require("./isArray")
local types = require("@packages/ES7Types")
local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>

return function<T>(value: Array<T>): T?
	if __DEV__ and not isArray(value) then
		error(string.format("Array.shift called on non-array %s", typeof(value)))
	end

	return if #value > 0 then table.remove(value, 1) else nil
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>

-- Implements Javascript's `Array.prototype.slice` as defined below, but with 1-indexing
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
return function<T>(t: Array<T>, start_idx: number?, end_idx: number?): Array<T>
	if type(t) ~= "table" then
		error(string.format("Array.slice called on %s", typeof(t)))
	end

	local length = #t

	local start_idx_ = start_idx or 1
	local end_idx_
	if end_idx == nil or end_idx > length + 1 then
		end_idx_ = length + 1
	else
		end_idx_ = end_idx
	end

	if start_idx_ > length + 1 then
		return {}
	end

	local slice = {}

	if start_idx_ < 1 then
		start_idx_ = math.max(length - math.abs(start_idx_), 1)
	end
	if end_idx_ < 1 then
		end_idx_ = math.max(length - math.abs(end_idx_), 1)
	end

	local idx = start_idx_
	local i = 1
	while idx < end_idx_ do
		slice[i] = t[idx]
		idx = idx + 1
		i = i + 1
	end

	return slice
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>
type Object = types.Object

-- note: JS version can return anything that's truthy, but that won't work for us since Lua deviates (0 is truthy)
type callbackFn<T> = (element: T, index: number, array: Array<T>) -> boolean
type callbackFnWithThisArg<T, U> = (thisArg: U, element: T, index: number, array: Array<T>) -> boolean

-- Implements Javascript's `Array.prototype.map` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
return function<T, U>(t: Array<T>, callback: callbackFn<T> | callbackFnWithThisArg<T, U>, thisArg: U?): boolean
	if type(t) ~= "table" then
		error(string.format("Array.some called on %s", typeof(t)))
	end

	if type(callback) ~= "function" then
		error("callback is not a function")
	end

	for i, value in t do
		if thisArg ~= nil then
			if value ~= nil and (callback :: callbackFnWithThisArg<T, U>)(thisArg, value, i, t) then
				return true
			end
		else
			if value ~= nil and (callback :: callbackFn<T>)(value, i, t) then
				return true
			end
		end
	end

	return false
end


--!optimize 2
--!strict

local None = require("../Object/None")
local types = require("@packages/ES7Types")

type Array<T> = types.Array<T>
type Comparable = (any, any) -> number
local defaultSort = function<T>(a: T, b: T): boolean
	return type(a) .. tostring(a) < type(b) .. tostring(b)
end

return function<T>(array: Array<T>, compare: Comparable?): Array<T>
	-- wrapperCompare interprets compare return value to be compatible with Lua's table.sort
	local wrappedCompare = defaultSort
	if compare ~= nil and compare ~= None then
		if type(compare :: any) ~= "function" then
			error("invalid argument to Array.sort: compareFunction must be a function")
		end

		wrappedCompare = function<T>(a: T, b: T)
			local result = compare(a, b)
			if type(result) ~= "number" then
				-- deviation: throw an error because
				-- it's not clearly defined what is
				-- the behavior when the compare function
				-- does not return a number
				error(`invalid result from compare function, expected number but got {typeof(result)}`)
			end

			return result < 0
		end
	end

	table.sort(array, wrappedCompare)
	return array
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>

-- Implements equivalent functionality to JavaScript's `array.splice`, including
-- the interface and behaviors defined at:
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
return function<T>(array: Array<T>, start: number, deleteCount: number?, ...: T): Array<T>
	-- Append varargs without removing anything
	if start > #array then
		local varargCount = select("#", ...)
		for i = 1, varargCount do
			local toInsert = select(i, ...)
			table.insert(array, toInsert)
		end

		return {}
	else
		local length = #array
		-- In the JS impl, a negative fromIndex means we should use length -
		-- index; with Lua, of course, this means that 0 is still valid, but
		-- refers to the end of the array the way that '-1' would in JS
		if start < 1 then
			start = math.max(length - math.abs(start), 1)
		end

		local deletedItems = {} :: Array<T>
		-- If no deleteCount was provided, we want to delete the rest of the
		-- array starting with `start`
		local deleteCount_: number = deleteCount or length
		if deleteCount_ > 0 then
			local lastIndex = math.min(length, start + math.max(0, deleteCount_ - 1))

			for _ = start, lastIndex do
				local deleted = table.remove(array, start) :: T
				table.insert(deletedItems, deleted)
			end
		end

		local varargCount = select("#", ...)
		-- Do this in reverse order so we can always insert in the same spot
		for i = varargCount, 1, -1 do
			local toInsert = select(i, ...)
			table.insert(array, start, toInsert)
		end

		return deletedItems
	end
end


--!optimize 2
--!strict

local isArray = require("./isArray")
local types = require("@packages/ES7Types")

local __DEV__ = _G.__DEV__
type Array<T> = types.Array<T>

return function<T>(array: Array<T>, ...: T): number
	if __DEV__ and not isArray(array) then
		error(string.format("Array.unshift called on non-array %s", typeof(array)))
	end

	local numberOfItems = select("#", ...)
	if numberOfItems > 0 then
		for i = numberOfItems, 1, -1 do
			local toInsert = select(i, ...)
			table.insert(array, 1, toInsert)
		end
	end

	return #array
end


--!optimize 2
--!strict

local ES7Types = require("@packages/ES7Types")
local InstanceOf = require("@packages/InstanceOf")

local arrayForEach = require("../Array/forEach")
local arrayMap = require("../Array/map")
local isArray = require("../Array/isArray")

local __DEV__ = _G.__DEV__

type Object = ES7Types.Object
type Array<T> = ES7Types.Array<T>
type Table<T, V> = ES7Types.Table<T, V>
type Tuple<T, V> = ES7Types.Tuple<T, V>
type mapCallbackFn<K, V> = ES7Types.mapCallbackFn<K, V>
type mapCallbackFnWithThisArg<K, V> = ES7Types.mapCallbackFnWithThisArg<K, V>
type Map<K, V> = ES7Types.Map<K, V>

type Map_Statics = {
	new: <K, V>(iterable: Array<Array<any>>?) -> Map<K, V>,
}

local Map: Map<any, any> & Map_Statics = ({} :: any) :: Map<any, any> & Map_Statics

function Map.new<K, V>(iterable: (Array<Array<any>> | Map<K, V>)?): Map<K, V>
	local array
	local map
	if iterable == nil then
		array = {}
		map = {}
	elseif isArray(iterable) then
		if __DEV__ then
			if #(iterable :: Array<Array<any>>) > 0 and type((iterable :: Array<Array<any>>)[1]) ~= "table" then
				error(
					"Value `"
						.. typeof((iterable :: Array<Array<any>>)[1])
						.. "` is not an entry object.\n "
						.. "Cannot create Map from {K, V} form, it must be { {K, V}... }"
				)
			end
		end

		array = table.create(#(iterable :: Array<Array<any>>))
		map = {}
		for _, entry in iterable :: Array<Array<any>> do
			local key = entry[1]
			if __DEV__ and key == nil then
				error("cannot create Map from a table that isn't an array.")
			end

			local val = entry[2]
			-- only add to array if new
			if map[key] == nil then
				table.insert(array, key)
			end

			-- always assign
			map[key] = val
		end
	elseif InstanceOf(iterable, Map) then
		array = table.clone((iterable :: Map<K, V>)._array)
		map = table.clone((iterable :: Map<K, V>)._map)
	else
		error(string.format("`%*` `%*` is not iterable, cannot make Map using it", typeof(iterable), iterable))
	end

	return (setmetatable({
		size = #array;
		_map = map;
		_array = array;
	}, Map) :: any) :: Map<K, V>
end

-- TODO Luau: annoying type erasure here, probably needs the new Records language feature
function Map:set(key: any, value: any): Map<any, any>
	-- preserve initial insertion order
	if self._map[key] == nil then
		-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
		self.size += 1
		table.insert(self._array, key)
	end

	-- always update value
	self._map[key] = value
	return self
end

function Map:get(key)
	return self._map[key]
end

function Map:clear()
	local table_: any = table
	self.size = 0
	table_.clear(self._map)
	table_.clear(self._array)
end

function Map:delete(key): boolean
	if self._map[key] == nil then
		return false
	end

	-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
	self.size -= 1
	self._map[key] = nil
	local index = table.find(self._array, key)
	if index then
		table.remove(self._array, index)
	end

	return true
end

-- Implements Javascript's `Map.prototype.forEach` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach
function Map:forEach(callback: mapCallbackFn<any, any> | mapCallbackFnWithThisArg<any, any>, thisArg: Object?): ()
	if __DEV__ or type(callback) ~= "function" then
		error("callback is not a function")
	end

	-- note: we can't turn this into a simple for-in loop, because the callbacks can modify the table and React, GQL, and Jest rely on JS behavior in that scenario
	arrayForEach(self._array, function(key)
		local value = self._map[key]

		if thisArg ~= nil then
			(callback :: mapCallbackFnWithThisArg<any, any>)(thisArg, value, key, self)
		else
			(callback :: mapCallbackFn<any, any>)(value, key, self)
		end
	end)
end

function Map:has(key): boolean
	return self._map[key] ~= nil
end

function Map:keys()
	return self._array
end

function Map:values()
	return arrayMap(self._array, function(key)
		return self._map[key]
	end)
end

function Map:entries()
	return arrayMap(self._array, function(key)
		return {key, self._map[key]}
	end)
end

function Map:ipairs()
	if __DEV__ then
		warn(
			debug.traceback(
				"`for _,_ in myMap:ipairs() do` is deprecated and will be removed in a future release, please use `for _,_ in myMap do` instead\n",
				2
			)
		)
	end

	return ipairs(self:entries())
end

function Map.__iter(self: Map<any, any>)
	return next, self:entries()
end

function Map.__index(self: Map<any, any>, key)
	local mapProp = rawget(Map, key)
	if mapProp ~= nil then
		return mapProp
	end

	if __DEV__ then
		assert(
			-- FIXME Luau: shouldn't need this cast, the self param is annotated already
			rawget(self :: any, "_map"),
			"Map has been corrupted, and is missing private state! Did you accidentally call table.clear() instead of map:clear()?"
		)
	end

	return Map.get(self, key)
end

function Map.__newindex(self: Map<any, any>, key, value)
	self:set(key, value)
end

return Map


--!optimize 2
--!strict

local Map = require("./Map")
local Object = require("../Object")
local instanceOf = require("@packages/InstanceOf")
local types = require("@packages/ES7Types")

type Map<K, V> = types.Map<K, V>
type Table<K, V> = types.Table<K, V>

local function coerceToMap(mapLike: Map<any, any> | Table<any, any>): Map<any, any>
	return instanceOf(mapLike, Map) and mapLike :: Map<any, any> -- ROBLOX: order is preserved
		or Map.new(Object.entries(mapLike)) -- ROBLOX: order is not preserved
end

return coerceToMap


--!optimize 2
--!strict

local Map = require("./Map")
local arrayReduce = require("../Array/reduce")
local instanceOf = require("@packages/InstanceOf")
local types = require("@packages/ES7Types")

type Map<K, V> = types.Map<K, V>
type Table<K, V> = types.Table<K, V>

local function coerceToTable(mapLike: Map<any, any> | Table<any, any>): Table<any, any>
	if not instanceOf(mapLike, Map) then
		return mapLike :: Table<any, any>
	end

	-- create table from map
	return arrayReduce(mapLike:entries(), function(tbl, entry)
		tbl[entry[1]] = entry[2]
		return tbl
	end, {})
end

return coerceToTable


--!optimize 2
--!strict

local ES7Types = require("@packages/ES7Types")
local Map = require("@self/Map")
local coerceToMap = require("@self/coerceToMap")
local coerceToTable = require("@self/coerceToTable")

export type Map<K, V> = ES7Types.Map<K, V>

return table.freeze({
	Map = Map;
	coerceToMap = coerceToMap;
	coerceToTable = coerceToTable;
})


--!optimize 2
--!strict

-- Marker used to specify that the value is nothing, because nil cannot be
-- stored in tables.

local None = newproxy(true)
local metatable = getmetatable(None)
metatable.__tostring = function()
	return "Object.None"
end

return None


--!optimize 2
--!

local None = require("./None")
local types = require("@packages/ES7Types")

type Object = types.Object

--[[
	Merges values from zero or more tables onto a target table. If a value is
	set to None, it will instead be removed from the table.

	This function is identical in functionality to JavaScript's Object.assign.
]]
-- Luau TODO: no way to strongly type this accurately, it doesn't eliminate deleted keys of T, and Luau won't do intersections of type packs: <T, ...U>(T, ...: ...U): T & ...U
return function<T, U, V, W>(target: T, source0: U?, source1: V?, source2: W?, ...): T & U & V & W
	if source0 ~= nil and type(source0 :: any) == "table" then
		for key, value in pairs(source0 :: any) do
			if value == None then
				(target :: any)[key] = nil
			else
				(target :: any)[key] = value
			end
		end
	end

	if source1 ~= nil and type(source1 :: any) == "table" then
		for key, value in pairs(source1 :: any) do
			if value == None then
				(target :: any)[key] = nil
			else
				(target :: any)[key] = value
			end
		end
	end

	if source2 ~= nil and type(source2 :: any) == "table" then
		for key, value in pairs(source2 :: any) do
			if value == None then
				(target :: any)[key] = nil
			else
				(target :: any)[key] = value
			end
		end
	end

	for index = 1, select("#", ...) do
		local rest = select(index, ...)

		if rest ~= nil and type(rest) == "table" then
			for key, value in pairs(rest) do
				if value == None then
					(target :: any)[key] = nil
				else
					(target :: any)[key] = value
				end
			end
		end
	end

	-- TODO? we can add & Object to this, if needed by real-world code, once CLI-49825 is fixed
	return (target :: any) :: T & U & V & W
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>
type Object = types.Object

return function<T>(t: T & (Object | Array<any>)): T
	-- Luau FIXME: model freeze better so it passes through the type constraint and doesn't erase
	return (table.freeze(t :: any) :: any) :: T
end


--!optimize 2
--!strict

return table.freeze({
	assign = require("@self/assign");
	entries = require("@self/entries");
	freeze = require("@self/freeze");
	is = require("@self/is");
	isFrozen = require("@self/isFrozen");
	keys = require("@self/keys");
	preventExtensions = require("@self/preventExtensions");
	seal = require("@self/seal");
	values = require("@self/values");
	-- Special marker type used in conjunction with `assign` to remove values
	-- from tables, since nil cannot be stored in a table
	None = require("@self/None");
})


--!optimize 2
--!strict

-- Implements Javascript's `Object.is` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is

return function(value1: any, value2: any): boolean
	return if value1 == value2 then value1 ~= 0 or 1 / value1 == 1 / value2 else value1 ~= value1 and value2 ~= value2
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>
type Object = types.Object

local __DEV__ = _G.__DEV__

return function(t: Object | Array<any>): boolean
	if __DEV__ then
		print("Luau now has a direct table.isfrozen call that can save the overhead of this library function call")
	end

	return table.isfrozen(t)
end


--!optimize 2
--!strict

local ES7Types = require("@packages/ES7Types")
local InstanceOf = require("@packages/InstanceOf")
local Set = require("../Set")

type Array<T> = ES7Types.Array<T>
type Set<T> = ES7Types.Set<T>
type Table = {[any]: any}

return function(value: Set<any> | Table | string): Array<string>
	if value == nil then
		error("cannot extract keys from a nil value")
	end

	local valueType = type(value)

	local keys
	if valueType == "table" then
		keys = {}
		if InstanceOf(value, Set) then
			return keys
		end

		for key in pairs(value :: Table) do
			table.insert(keys, key)
		end
	elseif valueType == "string" then
		local length = #(value :: string)
		keys = table.create(length)
		for i = 1, length do
			keys[i] = tostring(i)
		end
	end

	return keys
end


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>
type Object = types.Object

-- FIXME: This should be updated to be closer to the actual
-- `Object.preventExtensions` functionality in JS. This requires additional
-- support from the VM
local function preventExtensions<T>(t: T & (Object | Array<any>)): T
	local name = tostring(t)

	return (
		setmetatable(t :: any, {
			__newindex = function(_, key)
				error(string.format(`%q ({typeof(key)}) is not a valid member of {name}`, tostring(key)), 2)
			end;
			__metatable = false;
		}) :: any
	) :: T
end

return preventExtensions


--!optimize 2
--!strict

local types = require("@packages/ES7Types")
type Array<T> = types.Array<T>

-- TODO Luau: needs overloads to model this more correctly
return function<T>(value: {[string]: T} | Array<T> | string): Array<T> | Array<string>
	if value == nil then
		error("cannot extract values from a nil value")
	end

	local valueType = type(value)
	local array: Array<T> | Array<string>
	if valueType == "table" then
		array = {} :: Array<T>
		for _, keyValue in pairs(value :: {[string]: T} | Array<T>) do
			table.insert(array, keyValue)
		end
	elseif valueType == "string" then
		-- optimization to avoid rehashing/growth
		local valueStringLength = #(value :: string)
		array = table.create(valueStringLength)
		for i = 1, valueStringLength do
			(array :: Array<string>)[i] = string.sub(value :: string, i, i)
		end
	end

	return array
end


--!optimize 2
--!nonstrict

local arrayForEach = require("./Array/forEach")
local arrayFromString = require("./Array/fromString")
local inspect = require("./inspect")
local isArray = require("./Array/isArray")
local types = require("@packages/ES7Types")

local __DEV__ = _G.__DEV__

type Array<T> = types.Array<T>
type Object = types.Object
type setCallbackFn<T> = types.setCallbackFn<T>
type setCallbackFnWithThisArg<T> = types.setCallbackFnWithThisArg<T>
export type Set<T> = types.Set<T>

type Set_Statics = {
	new: <T>(iterable: Array<T> | Set<T> | string | nil) -> Set<T>,
}

local Set: Set<any> & Set_Statics = (
	{
		__iter = function(self)
			return next, self._array
		end;

		__tostring = function(self)
			local result = "Set "
			if #self._array > 0 then
				result ..= `({#self._array})`
			end

			result ..= inspect(self._array)
			return result
		end;
	} :: any
) :: Set<any> & Set_Statics
(Set :: any).__index = Set

function Set.new<T>(iterable: Array<T> | Set<T> | string | nil): Set<T>
	local array
	local map = {}
	if iterable ~= nil then
		local arrayIterable

		if type(iterable) == "table" then
			if isArray(iterable) then
				arrayIterable = table.clone(iterable)
			else
				local mt = getmetatable(iterable :: any)
				if mt and rawget(mt, "__iter") then
					arrayIterable = iterable :: Set<T>
				elseif __DEV__ then
					error("cannot create array from an object-like table")
				end
			end
		elseif type(iterable) == "string" then
			-- TODO Luau: need overloads for `from` to avoid needing the manual cast
			arrayIterable = arrayFromString(iterable :: string) :: Array<string>
		else
			error(string.format("cannot create array from value of type `%*`", typeof(iterable)))
		end

		if arrayIterable then
			array = table.create(#arrayIterable)
			for _, element in arrayIterable do
				if not map[element] then
					map[element] = true
					table.insert(array, element)
				end
			end
		else
			array = {}
		end
	else
		array = {}
	end

	return (setmetatable({
		size = #array;
		_map = map;
		_array = array;
	}, Set) :: any) :: Set<T>
end

function Set:add(value)
	if not self._map[value] then
		-- Luau FIXME: analyze should know self is Set<T> which includes size as a number
		self.size += 1
		self._map[value] = true
		table.insert(self._array, value)
	end

	return self
end

function Set:clear()
	self.size = 0
	table.clear(self._map)
	table.clear(self._array)
end

function Set:delete(value): boolean
	if not self._map[value] then
		return false
	end

	-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
	self.size -= 1
	self._map[value] = nil
	local index = table.find(self._array, value)
	if index then
		table.remove(self._array, index)
	end

	return true
end

-- Implements Javascript's `Map.prototype.forEach` as defined below
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/forEach
function Set:forEach(callback: setCallbackFn<any> | setCallbackFnWithThisArg<any>, thisArg: Object?): ()
	if type(callback) ~= "function" then
		error("callback is not a function")
	end

	-- note: we can't turn this into a simple for-in loop, because the callbacks can modify the table and React, GQL, and Jest rely on JS behavior in that scenario
	arrayForEach(self._array, function(value)
		if thisArg ~= nil then
			(callback :: setCallbackFnWithThisArg<any>)(thisArg, value, value, self)
		else
			(callback :: setCallbackFn<any>)(value, value, self)
		end
	end)
end

function Set:has(value): boolean
	return self._map[value] ~= nil
end

function Set:ipairs()
	if __DEV__ then
		warn(
			debug.traceback(
				"`for _,_ in mySet:ipairs() do` is deprecated and will be removed in a future release, please use `for _,_ in mySet do` instead\n",
				2
			)
		)
	end

	return ipairs(self._array)
end

return Set


--!optimize 2
--!strict

local ES7Types = require("@packages/ES7Types")
type WeakMap<K, V> = ES7Types.WeakMap<K, V>

type WeakMapPrivate<K, V> = {
	_weakMap: {[K]: V},
	-- method definitions
	get: (self: WeakMapPrivate<K, V>, K) -> V,
	set: (self: WeakMapPrivate<K, V>, K, V) -> WeakMapPrivate<K, V>,
	has: (self: WeakMapPrivate<K, V>, K) -> boolean,
}
type WeakMap_Statics = {
	new: <K, V>() -> WeakMap<K, V>,
}

local WeakMap: WeakMapPrivate<any, any> & WeakMap_Statics = {} :: any
(WeakMap :: any).__index = WeakMap

function WeakMap.new<K, V>(): WeakMap<K, V>
	local weakMap = setmetatable({}, {__mode = "k"})
	return setmetatable({_weakMap = weakMap}, WeakMap) :: any
end

function WeakMap:get(key)
	return self._weakMap[key]
end

function WeakMap:set(key, value)
	self._weakMap[key] = value
	return self
end

function WeakMap:has(key): boolean
	return self._weakMap[key] ~= nil
end

return WeakMap :: WeakMap<any, any> & WeakMap_Statics


--!optimize 2
--!strict

local Array = require("@self/Array")
local Map = require("@self/Map")
local Object = require("@self/Object")
local Set = require("@self/Set")
local WeakMap = require("@self/WeakMap")
local inspect = require("@self/inspect")
local types = require("@packages/ES7Types")

export type Array<T> = types.Array<T>
export type Map<T, V> = types.Map<T, V>
export type Object = types.Object
export type Set<T> = types.Set<T>
export type WeakMap<T, V> = types.WeakMap<T, V>

return table.freeze({
	Array = Array;
	Object = Object;
	Map = Map.Map;
	coerceToMap = Map.coerceToMap;
	coerceToTable = Map.coerceToTable;
	Set = Set;
	WeakMap = WeakMap;
	inspect = inspect;
})


--!optimize 2
--!strict

local Chalk = require("@packages/Chalk")
local Collections = require("../Collections")
local Debug = require("@packages/Debug")

local Map = Collections.Map
local inspect = Collections.inspect

local INDENT = "  "

local RED = Chalk.Rgb(243, 53, 53)
local YELLOW = Chalk.Rgb(243, 174, 53)

local STRING_FORMAT_ARGUMENTS: {string} = {"c", "d", "i", "o", "u", "x", "X", "e", "E", "f", "g", "G", "q", "s", "*"}
local FORMAT_ARGUMENTS_MATCH = (function(): string
	local array = {}
	local set = {}
	local length = 0

	for _, key in STRING_FORMAT_ARGUMENTS do
		if set[key] then
			continue
		end

		length += 1
		array[length] = Debug.EscapeString(key)
		set[key] = true
	end

	return `%%[{table.concat(array)}]`
end)()

local function Pack<T...>(...: T...): {[number]: any, n: number}
	-- selene: allow(mixed_table)
	return {
		n = select("#", ...);
		select(1, ...);
	}
end
local function HasStringFormatArguments(formatString: string): boolean
	return string.match(formatString, FORMAT_ARGUMENTS_MATCH) ~= nil
end

type Memoized<T, U> = typeof(setmetatable(
	{} :: {[T]: U},
	{} :: {
		__call: (self: any, index: T) -> U,
		__index: (self: any, index: T) -> U,
	}
))

local function Memoize<T, U>(callback: (index: T) -> U): Memoized<T, U>
	local metatable = {}
	function metatable:__call(index: T): U
		return self[index]
	end

	function metatable:__index(index: T): U
		local value = callback(index)
		self[index] = value
		return value
	end

	return setmetatable({}, metatable) :: Memoized<T, U>
end

local BOLD_GRAY = Chalk.Bold .. Chalk.Gray

local BOLD_INVERSE = Chalk.Bold .. Chalk.Inverse
local BOLD_INVERSES = Memoize(function(color: Chalk.Styler): Chalk.Styler
	return BOLD_INVERSE .. color
end)

local function GetTag(functionName: string, styler: Chalk.Styler?): string
	local tag = ` {string.upper(functionName)} `
	return `{BOLD_GRAY("[")}{if styler then BOLD_INVERSES[styler](tag) else tag}{BOLD_GRAY("]")}`
end

-- local Warn = (function(): (...unknown) -> ()
-- 	if USE_GOOD_WARN then
-- 		local function GoodWarn(...: unknown): ()
-- 			print(YELLOW(Concat(GetTag("Warn", YELLOW), ...)))
-- 		end
-- 		return GoodWarn
-- 	end

-- 	local function BadWarn(...: unknown): ()
-- 		warn(YELLOW(Concat(...)))
-- 	end
-- 	return BadWarn
-- end)()

-- local function ErrorWarn(...: unknown): ()
-- 	print(RED(Concat(GetTag("Error", RED), ...)))
-- end

local function MakeConsole()
	local console = {}
	local indentDepth = 0

	local function indent(): string
		return string.rep(INDENT, indentDepth)
	end

	local function createPrint(name: string, styler: Chalk.Styler?, editsIndent: boolean?): (...unknown) -> ()
		local prefix = GetTag(name, styler)

		local function logFunction(...: unknown): ()
			local length = select("#", ...)
			if length == 0 then
				print()
				if editsIndent then
					indentDepth += 1
				end
				return
			end

			local content = (...)
			if type(content) == "string" and HasStringFormatArguments(content) then
				local arguments = Pack(...)
				local success, value = pcall(string.format, content, table.unpack(arguments, 2, arguments.n))
				if success then
					print(indent() .. prefix, value)
					if editsIndent then
						indentDepth += 1
					end
					return
				end
			end

			local array = table.create(length)
			for index = 1, length do
				local value = select(index, ...)
				array[index] = if type(value) == "string" then value else inspect(value)
			end
			print(indent() .. prefix, table.concat(array, " "))
			if editsIndent then
				indentDepth += 1
			end
		end

		return logFunction
	end

	console.log = createPrint("log", Chalk.White)
	console.info = createPrint("info", Chalk.Blue)
	console.debug = createPrint("debug", Chalk.Cyan)
	console.warn = createPrint("warn", YELLOW or Chalk.Yellow)
	console.error = createPrint("error", RED or Chalk.Red)
	console.group = createPrint("group", Chalk.Magenta, true)
	console.groupCollapsed = createPrint("groupCollapsed", Chalk.Magenta .. Chalk.Dim, true)

	function console.groupEnd(): ()
		if indentDepth > 0 then
			indentDepth -= 1
		end
	end

	local startTimes: Collections.Map<string, number> = Map.new()
	function console.time(label: string?): ()
		local trueLabel = label or "default"
		if startTimes:has(trueLabel) then
			console.warn(`Timer '{trueLabel}' already exists`)
			return
		end

		startTimes:set(trueLabel, os.clock())
	end
	function console.timeEnd(label: string?): ()
		local finishTime = os.clock()
		local trueLabel = label or "default"
		local startTime = startTimes:get(trueLabel)
		if startTime == nil then
			console.warn(`Timer '{trueLabel}' does not exist`)
			return
		end

		console.log(`{trueLabel}: {(finishTime - startTime) * 1000} ms`)
		startTimes:delete(trueLabel)
	end

	return table.freeze(console)
end

return MakeConsole


--!optimize 2
--!strict

local MakeConsole = require("@self/MakeConsole")

local console = MakeConsole()
return console


--!optimize 2
--!strict

local ES7Types = require("@packages/ES7Types")
type Function = ES7Types.Function

export type Error = {
	name: string,
	message: string,
	stack: string?,
}
type Private = Error & {__stack: string?}

local Error = {}

local DEFAULT_NAME = "Error"
Error.__index = Error
Error.__tostring = function(self)
	-- Luau FIXME: I can't cast to Error or Object here: Type 'Object' could not be converted into '{ @metatable *unknown*, {|  |} }'
	return getmetatable(Error :: any).__tostring(self)
end

local function RecalculateStacktrace(self: Error)
	local exception = self :: Private
	local message = exception.message
	local name = exception.name or DEFAULT_NAME

	local errorName = name .. (if message ~= nil and message ~= "" then ": " .. message else "")
	local stack = exception.__stack or ""

	local newStackString = string.split(stack, "\n")
	local length = #newStackString
	if newStackString[length] == "" then
		newStackString[length] = nil
	end

	for index, stackLine in newStackString do
		newStackString[index] = `\tat {stackLine}`
	end

	exception.stack = errorName .. "\n" .. table.concat(newStackString, "\n")
end

local function CaptureStackTrace(self: Error, level: number, options: Function?)
	local exception = self :: Private
	if type(options) == "function" then
		local stack = debug.traceback(nil, level)
		local functionName: string = debug.info(options, "n")
		local sourceFilePath: string = debug.info(options, "s")

		local escapedSourceFilePath = string.gsub(sourceFilePath, "([%(%)%.%%%+%-%*%?%[%^%$])", "%%%1")
		local stackTraceLinePattern = escapedSourceFilePath .. ":%d* function " .. functionName
		local start = string.find(stack, stackTraceLinePattern)
		local finish = nil
		if start ~= nil then
			start, finish = string.find(stack, "\n", start + 1)
		end

		if finish ~= nil then
			stack = string.sub(stack, finish + 1)
		end

		exception.__stack = stack
	else
		exception.__stack = debug.traceback(nil, level)
	end

	RecalculateStacktrace(exception)
end

-- ROBLOX NOTE: extracted __createError function so that both Error.new() and Error() can capture the stack trace at the same depth
local function CreateError(message: string?): Error
	local self: Error = setmetatable({
		name = DEFAULT_NAME;
		message = message or "";
	}, Error) :: never

	CaptureStackTrace(self, 4)
	return self
end

function Error.new(message: string?): Error
	return CreateError(message)
end

function Error.captureStackTrace(exception: Error, options: Function?)
	CaptureStackTrace(exception, 3, options)
end

return setmetatable(Error, {
	__call = function(_, ...)
		return CreateError(...)
	end;
	__tostring = function(self)
		local stack = self.stack
		if stack and stack ~= "" then
			return stack
		end

		local name = self.name
		if name ~= nil then
			local message = self.message
			if message and message ~= "" then
				return string.format("%*: %*", name, message)
			end

			return tostring(name)
		end

		return tostring(DEFAULT_NAME)
	end;
})


--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER

return 9007199254740991


--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER

return -9007199254740991


--!optimize 2
--!strict

return table.freeze({
	isFinite = require("@self/isFinite");
	isInteger = require("@self/isInteger");
	isNaN = require("@self/isNaN");
	isSafeInteger = require("@self/isSafeInteger");
	MAX_SAFE_INTEGER = require("@self/MAX_SAFE_INTEGER");
	MIN_SAFE_INTEGER = require("@self/MIN_SAFE_INTEGER");
	NaN = 0 / 0;
	toExponential = require("@self/toExponential");
})


--!optimize 2
--!strict

return function(value: unknown)
	return type(value) == "number" and value == value and value ~= math.huge and value ~= -math.huge
end


--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger

return function(value: unknown)
	return type(value) == "number" and value ~= math.huge and value == math.floor(value)
end


--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN

return function(value: unknown)
	return type(value) == "number" and value ~= value
end


--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger

local MAX_SAFE_INTEGER = require("./MAX_SAFE_INTEGER")
local isInteger = require("./isInteger")

return function(value: unknown)
	return isInteger(value) and math.abs(value :: number) <= MAX_SAFE_INTEGER
end


--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential

return function(value: number | string, fractionDigits: number?): string?
	local num = value
	if type(value) == "string" then
		-- ROBLOX FIXME: add parseInt to encapsulate this logic and use it here
		local NaN = 0 / 0
		num = tonumber(value) or NaN -- works because 0 is truthy in Lua
	end

	-- returns nil for invalid input instead of nan to be more lua-native
	if type(num) ~= "number" then
		return "nan"
	end

	if fractionDigits ~= nil then
		if type(fractionDigits) ~= "number" then
			error("TypeError: fractionDigits must be a number between 0 and 100")
		end

		if fractionDigits < 0 or fractionDigits > 100 then
			error("RangeError: fractionDigits must be between 0 and 100")
		end
	end

	local formatString = if fractionDigits == nil then "%e" else `%.{fractionDigits}e`
	return (string.gsub(string.gsub(string.gsub(string.format(formatString, num), "%+0", "+"), "%-0", "-"), "0*e", "e"))
end


--!optimize 2
-- this maps onto community promise libraries which won't support Luau, so we inline

local Promise = require("@packages/Promise")

export type PromiseLike<T> = {
	Then: (
		self: PromiseLike<T>,
		resolve: ((T) -> ...(nil | T | PromiseLike<T>))?,
		reject: ((any) -> ...(nil | T | PromiseLike<T>))?
	) -> PromiseLike<T>,
}

export type Promise<T...> = Promise.TypedPromise<T...>

return {}


--!optimize 2
--!strict

local Number = require("../Number")
local NaN = Number.NaN

-- js  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt
-- lua http://www.lua.org/manual/5.4/manual.html#pdf-utf8.codepoint
return function(value: string, index: number): number
	if type(index) ~= "number" then
		index = 1
	end

	--[[
		Initial bounds check. Checking string.len is not an exhaustive upper bound,
		but it is cheaper to check string.len and handle utf8.offset than to check
		utf.len, which iterates over all codepoints.
	]]
	local length = #value
	if index < 1 or index > length then
		return NaN
	end

	-- utf8.offset returns nil for out of bounds
	local offset = utf8.offset(value, index)

	-- check that offset is not greater than the length of the string
	if offset == nil or offset > length then
		return NaN
	end

	return utf8.codepoint(value, offset, offset) or NaN
end


--!optimize 2
--!strict

local function endsWith(value: string, substring: string, optionalLength: number?): boolean
	local substringLength = #substring
	if substringLength == 0 then
		return true
	end

	local valueLength = #value
	local length = optionalLength or valueLength
	if length > valueLength then
		length = valueLength
	end

	if length < 1 then
		return false
	end

	local position = length - substringLength + 1
	return string.find(value, substring, position, true) == position
end

return endsWith


--!optimize 2
--!strict

type Match = {
	index: number,
	match: string,
}

-- excluding the `+` and `*` character, since findOr tests and graphql use them explicitly
local luauPatternCharacters = "([" .. string.gsub("$%^()-[].?", "(.)", "%%%1") .. "])"

local function findOr(value: string, patternTable: {string}, initialIndex: number?): Match?
	-- loop through all options in pattern patternTable

	local initial = utf8.offset(value, initialIndex or 1)
	local matches = {}
	local length = 0

	for _, pattern in patternTable do
		pattern = string.gsub(pattern, luauPatternCharacters, "%%%1")
		local startIndex, finishIndex = string.find(value, pattern, initial)
		if startIndex then
			local prefix = string.sub(value, 1, startIndex - 1)
			local prefixEnd, invalidBytePosition = utf8.len(prefix)
			if prefixEnd == nil then
				error(string.format("string `%*` has an invalid byte at position %*", prefix, invalidBytePosition))
			end

			local atIndex = prefixEnd :: number + 1
			length += 1
			matches[length] = {
				index = atIndex;
				match = string.sub(value, startIndex, finishIndex);
			}
		end
	end

	-- if no matches, return nil
	if length == 0 then
		return nil
	end

	-- find the first matched index (after the init param)
	-- for each, if we get a hit, return the earliest index and matched term

	local firstMatch
	for _, match in matches do
		-- load first condition
		if firstMatch == nil then
			firstMatch = match
		end

		-- identify if current match comes before first match
		if match.index < firstMatch.index then
			firstMatch = match
		end
	end

	-- return first match
	return firstMatch
end

return findOr


--!optimize 2
--!strict

-- excluding the `+` and `*` character, since findOr tests and graphql use them explicitly
local luauPatternCharacters = "([" .. string.gsub("$%^()-[].?", "(.)", "%%%1") .. "])"

local function includes(value: string, substring: string, position: nil | number | string): boolean
	local stringLength, invalidBytePosition = utf8.len(value)
	assert(
		stringLength ~= nil,
		string.format("string `%*` has an invalid byte at position %*", value, invalidBytePosition)
	)
	if stringLength == 0 then
		return false
	end

	if #substring == 0 then
		return true
	end

	local startIndex = 1
	if position ~= nil then
		startIndex = tonumber(position) or 1
		if startIndex > stringLength then
			return false
		end
	end

	if startIndex < 1 then
		startIndex = 1
	end

	local initial = utf8.offset(value, startIndex)
	return string.find(value, string.gsub(substring, luauPatternCharacters, "%%%1"), initial) ~= nil
end

return includes


--!optimize 2
--!strict

-- excluding the `+` and `*` character, since findOr tests and graphql use them explicitly
local luauPatternCharacters = "([" .. string.gsub("$%^()-[].?", "(.)", "%%%1") .. "])"

-- Implements equivalent functionality to JavaScript's `String.indexOf`,
-- implementing the interface and behaviors defined at:
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf

return function(value: string, searchElement: string, fromIndex: number?): number
	local length = #value
	local trueFromIndex = 1
	if fromIndex then
		trueFromIndex = if fromIndex < 1 then 1 else fromIndex :: number
	end

	if #searchElement == 0 then
		return if trueFromIndex > length then length else trueFromIndex
	end

	if trueFromIndex > length then
		return -1
	end

	searchElement = string.gsub(searchElement, luauPatternCharacters, "%%%1")
	local searchElementLength = #searchElement

	for index = trueFromIndex, length do
		if string.sub(value, index, index + searchElementLength - 1) == searchElement then
			return index
		end
	end

	return -1
end


--!optimize 2
--!strict

return table.freeze({
	charCodeAt = require("@self/charCodeAt");
	endsWith = require("@self/endsWith");
	findOr = require("@self/findOr");
	includes = require("@self/includes");
	indexOf = require("@self/indexOf");
	lastIndexOf = require("@self/lastIndexOf");
	slice = require("@self/slice");
	split = require("@self/split");
	startsWith = require("@self/startsWith");
	substr = require("@self/substr");
	trim = require("@self/trim");
	trimEnd = require("@self/trimEnd");
	trimStart = require("@self/trimStart");
	-- aliases for trimEnd and trimStart
	trimRight = require("@self/trimEnd");
	trimLeft = require("@self/trimStart");
})


--!optimize 2
--!strict

local function lastIndexOf(value: string, searchValue: string, fromIndex: number?): number
	local valueLength = #value
	local calculatedFromIndex
	if fromIndex then
		calculatedFromIndex = fromIndex
	else
		calculatedFromIndex = valueLength
	end

	if fromIndex and fromIndex < 1 then
		calculatedFromIndex = 1
	end

	if fromIndex and fromIndex > valueLength then
		calculatedFromIndex = valueLength
	end

	if searchValue == "" then
		-- FIXME: Luau DFA doesn't understand that
		return calculatedFromIndex :: number
	end

	local lastFoundStartIndex, foundStartIndex
	-- Luau FIXME: Luau doesn't look beyond assignment for type, it should infer number? from loop bound
	local foundEndIndex: number? = 0
	repeat
		lastFoundStartIndex = foundStartIndex
		-- Luau FIXME: DFA doesn't understand until clause means foundEndIndex is never nil within loop
		foundStartIndex, foundEndIndex = string.find(value, searchValue, foundEndIndex :: number + 1, true)
	until foundStartIndex == nil or foundStartIndex > calculatedFromIndex

	if lastFoundStartIndex == nil then
		return -1
	end

	-- Luau FIXME: Luau should see the predicate above and known the line below can only be a number
	return lastFoundStartIndex :: number
end

return lastIndexOf


--!optimize 2
--!strict

local function slice(value: string, startIndexStr: string | number, lastIndexStr: (string | number)?): string
	local strLen, invalidBytePosition = utf8.len(value)
	assert(strLen ~= nil, string.format("string `%*` has an invalid byte at position %*", value, invalidBytePosition))

	local startIndex = tonumber(startIndexStr)
	assert(type(startIndex) == "number", "startIndexStr should be a number")

	if startIndex + strLen < 0 then
		-- then |start index| is greater than string length
		startIndex = 1
	end

	if startIndex > strLen then
		return ""
	end

	-- if no last index length set, go to str length + 1
	local lastIndex = strLen + 1
	if lastIndexStr ~= nil then
		-- ROBLOX FIXME: add parseInt to encapsulate this logic and use it here
		local NaN = 0 / 0
		lastIndex = tonumber(lastIndexStr) or NaN -- this works because 0 is truthy in Lua
	end
	assert(type(lastIndex) == "number", "lastIndexStr should convert to number")

	if lastIndex > strLen then
		lastIndex = strLen + 1
	end

	local startIndexByte = utf8.offset(value, startIndex) :: number
	-- get char length of charset retunred at offset
	local lastIndexByte = (utf8.offset(value, lastIndex) :: number) - 1

	return string.sub(value, startIndexByte, lastIndexByte)
end

return slice


--!optimize 2
--!strict

local Number = require("../Number")
local findOr = require("./findOr")
local slice = require("./slice")

local MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER
type Array<T> = {T}

type Pattern = string | Array<string>

local function split(str: string, _pattern: Pattern?, _limit: number?): Array<string>
	if _pattern == nil then
		return {str}
	end

	if _limit == 0 then
		return {}
	end

	local limit = if _limit == nil or _limit < 0 then MAX_SAFE_INTEGER else _limit
	local pattern = _pattern
	local patternList: Array<string>
	if type(pattern) == "string" then
		if pattern == "" then
			return string.split(str, "")
		end

		patternList = {pattern}
	else
		patternList = pattern :: Array<string>
	end

	local init = 1
	local result = {}
	local lastMatch
	local strLen, invalidBytePosition = utf8.len(str)
	assert(strLen ~= nil, string.format("string `%*` has an invalid byte at position %*", str, invalidBytePosition))

	repeat
		local match = findOr(str, patternList, init)
		if match ~= nil then
			table.insert(result, slice(str, init, match.index))
			local matchLength = utf8.len(match.match)
			-- Luau FIXME? Luau doesn't understand that str has already been shown to be valid utf8 on line 26 and therefore won't be nil
			init = match.index + matchLength :: number
		else
			table.insert(result, slice(str, init, nil))
		end

		if match ~= nil then
			lastMatch = match
		end
	until match == nil or init > strLen or #result >= limit

	if lastMatch ~= nil then
		local lastMatchLength, invalidBytePosition_ = utf8.len(lastMatch.match)
		assert(
			lastMatchLength ~= nil,
			string.format("string `%*` has an invalid byte at position %*", lastMatch.match, invalidBytePosition_)
		)

		if lastMatch.index + lastMatchLength == strLen + 1 then
			table.insert(result, "")
		end
	end

	return result
end

return split


--!optimize 2
--!strict

local function startsWith(value: string, substring: string, position: number?): boolean
	if #substring == 0 then
		return true
	end
	-- Luau FIXME: we have to use a tmp variable, as Luau doesn't understand the logic below narrow position to `number`
	local position_
	if position == nil or position < 1 then
		position_ = 1
	else
		position_ = position
	end

	if position_ > #value then
		return false
	end

	return string.find(value, substring, position_, true) == position_
end

return startsWith


--!optimize 2
--!strict

return function(value: string, startIndex: number, numberOfCharacters: number?): string
	if numberOfCharacters and numberOfCharacters <= 0 then
		return ""
	end

	return string.sub(value, startIndex, numberOfCharacters and startIndex + numberOfCharacters - 1 or nil)
end


--!optimize 2
--!strict

local trimEnd = require("./trimEnd")
local trimStart = require("./trimStart")

return function(source: string): string
	return trimStart(trimEnd(source))
end


--!optimize 2
--!strict

return function(source: string): string
	return (string.gsub(source, "[%s]+$", ""))
end


--!optimize 2
--!strict

return function(source: string): string
	return (string.gsub(source, "^[%s]+", ""))
end


--!optimize 2
--!strict

local Symbol = require("./Symbol")

local GlobalRegistry: {[string]: Symbol.Symbol} = {}

return table.freeze({
	getOrInit = function(name: string): Symbol.Symbol
		if GlobalRegistry[name] == nil then
			GlobalRegistry[name] = Symbol.new(name)
		end

		return GlobalRegistry[name]
	end;

	-- Used for testing
	__clear = function()
		GlobalRegistry = {}
	end;
})


--!optimize 2
--!strict
--[[
	Symbols have the type 'userdata', but when printed or coerced to a string,
	the symbol will turn into the string given as its name.

	**This implementation provides only the `Symbol()` constructor and the
	global registry via `Symbol.for_`.**

	Other behaviors, including the ability to find all symbol properties on
	objects, are not implemented.
]]
export type Symbol = typeof(newproxy(true)) & {[string]: any}

return table.freeze({
	new = function(name: string?): Symbol
		local self = newproxy(true) :: any
		local wrappedName = if name then `Symbol({name})` else "Symbol()"

		getmetatable(self).__tostring = function()
			return wrappedName
		end

		return (self :: any) :: Symbol
	end;
})


--!optimize 2
--!strict
--[[
	A 'Symbol' is an opaque marker type, implemented to behave similarly to JS:
	https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
]]

local GlobalRegistry = require("@self/RegistryGlobal")
local Symbol = require("@self/Symbol")

export type Symbol = Symbol.Symbol
local SymbolObject = setmetatable({}, {
	--[[
		Creates a new symbol, using the given name when printed. Symbols are
		opaque, so this will always create a new, unique object
	]]
	__call = function(_, name: string?): Symbol.Symbol
		return Symbol.new(name)
	end;
})

SymbolObject.for_ = GlobalRegistry.getOrInit
SymbolObject["for"] = GlobalRegistry.getOrInit

return table.freeze(SymbolObject)


--!optimize 2
--!strict

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

local MakeInterval = require("@self/makeIntervalImpl")
local MakeTimer = require("@self/makeTimerImpl")

export type Timeout = MakeTimer.Timeout
export type Interval = MakeInterval.Interval

local Interval = MakeInterval(task.delay)
local Timer = MakeTimer(task.delay)

local Timers = table.freeze({
	clearInterval = Interval.clearInterval;
	setInterval = Interval.setInterval;

	clearTimeout = Timer.clearTimeout;
	setTimeout = Timer.setTimeout;
})

return Timers


--!optimize 2
--!strict

local Status = newproxy(false)

type TaskStatus = number
export type Interval = {[typeof(Status)]: TaskStatus}

local SCHEDULED = 1
local CANCELLED = 3

return function(taskDelay)
	local function setInterval(callback, intervalTime: number?, ...): Interval
		local arguments = {...}
		local interval = {
			[Status] = SCHEDULED;
		}

		-- intervalTime is an optional parameter
		-- To mimic the JS interface, we're expecting delayTime to be in ms
		local intervalTimeMs = (intervalTime or 0) / 1000
		local function execute(): ()
			taskDelay(intervalTimeMs, function()
				if interval[Status] == SCHEDULED then
					callback(table.unpack(arguments))
					execute()
				end
			end)
		end

		execute()

		return interval
	end

	local function clearInterval(interval: Interval)
		if interval == nil then
			return
		end

		if interval[Status] == SCHEDULED then
			interval[Status] = CANCELLED
		end
	end

	return table.freeze({
		clearInterval = clearInterval;
		setInterval = setInterval;
	})
end


--!optimize 2
local Status = newproxy(false)

type TaskStatus = number
export type Timeout = {[typeof(Status)]: TaskStatus}

local SCHEDULED = 1
local DONE = 2
local CANCELLED = 3

return function(taskDelay)
	local function setTimeout(callback, delayTime: number?, ...): Timeout
		local arguments = {...}
		local timeout = {
			[Status] = SCHEDULED;
		}

		-- delayTime is an optional parameter
		-- To mimic the JS interface, we're expecting delayTime to be in ms

		local delayTimeMs = (delayTime or 0) / 1000
		taskDelay(delayTimeMs, function()
			if timeout[Status] == SCHEDULED then
				callback(table.unpack(arguments))
				timeout[Status] = DONE
			end
		end)

		return timeout
	end

	local function clearTimeout(timeout: Timeout)
		if timeout == nil then
			return
		end

		if timeout[Status] == SCHEDULED then
			timeout[Status] = CANCELLED
		end
	end

	return table.freeze({
		clearTimeout = clearTimeout;
		setTimeout = setTimeout;
	})
end


--!optimize 2
--!strict
-- reference documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent

local Error = require("./Error")
local String = require("./String")
local net = require("@lune/net")

local charCodeAt = String.charCodeAt

local function encodeURIComponent(value: string): string
	local valueLength = utf8.len(value)
	if valueLength == 0 or valueLength == nil then
		return ""
	end

	-- we don't exhaustively test the whole string for invalid characters like ECMA-262 15.1.3 says
	local check = charCodeAt(value, 1)
	if valueLength == 1 then
		if check == 0xD800 then
			error(Error.new("URI malformed"))
		end

		if check == 0xDFFF then
			error(Error.new("URI malformed"))
		end
	end

	if check >= 0xDC00 and check < 0xDFFF then
		error(Error.new("URI malformed"))
	end

	local encoded = net.urlEncode(value)
	-- reverting encoded chars which are not encoded by JS

	local result = (
		string.gsub(
			string.gsub(
				string.gsub(
					string.gsub(
						string.gsub(
							string.gsub(
								string.gsub(string.gsub(string.gsub(encoded, "%%2D", "-"), "%%5F", "_"), "%%2E", "."),
								"%%21",
								"!"
							),
							"%%7E",
							"~"
						),
						"%%2A",
						"*"
					),
					"%%27",
					"'"
				),
				"%%28",
				"("
			),
			"%%29",
			")"
		)
	)
	return result
end

return encodeURIComponent


--!optimize 2
--!strict

--[[
	deviation: Our constructors currently have no notion of 'super' so any
	such behavior in upstream JS must be implemented manually by setting fields

	A constructor passed to this class would typically look along the lines of:
	function(self, arg, otherArg)
		self.arg = arg
		self.otherArg = otherArg
	end

	But even something like function() end can be passed in as the constructor
	if no fields need to be set. In upstream JS, the equivalent would be
	returning an empty object from the constructor.
]]

return function(parent, childName, constructor)
	local Child = {}
	Child.__index = Child
	Child.__tostring = function(self)
		return getmetatable(parent).__tostring(self)
	end

	local mt = {}

	Child.new = function(...)
		local self = {}
		constructor(self, ...)
		return setmetatable(self, Child)
	end

	if type(getmetatable(parent)) == "table" and getmetatable(parent).__call then
		mt.__call = function(_, ...)
			return Child.new(...)
		end
	end

	mt.__index = parent
	mt.__tostring = function(self)
		-- Printing class name
		if self == Child then
			return tostring(childName)
		end

		return getmetatable(parent).__tostring(self)
	end

	setmetatable(Child, mt)

	return Child
end


--!optimize 2
--!strict

local AssertionError = require("@self/AssertionError")
local Boolean = require("@self/Boolean")
local Collections = require("@self/Collections")
local Console = require("@self/Console")
local Error = require("@self/Error")
local Math = require("@self/Math")
local Number = require("@self/Number")
local PromiseModule = require("@self/Promise")
local String = require("@self/String")
local Symbol = require("@self/Symbol")
local Timers = require("@self/Timers")
local encodeURIComponent = require("@self/encodeURIComponent")
local extends = require("@self/extends")
local instanceof = require("@packages/InstanceOf")
local types = require("@packages/ES7Types")

export type Array<T> = types.Array<T>
export type AssertionError = AssertionError.AssertionError
export type Error = Error.Error
export type Map<T, V> = types.Map<T, V>
export type Object = types.Object

export type PromiseLike<T> = PromiseModule.PromiseLike<T>
export type Promise<T> = PromiseModule.Promise<T>

export type Set<T> = types.Set<T>
export type Symbol = Symbol.Symbol
export type Timeout = Timers.Timeout
export type Interval = Timers.Interval
export type WeakMap<T, V> = Collections.WeakMap<T, V>

return table.freeze({
	Array = Collections.Array;
	AssertionError = AssertionError;
	Boolean = Boolean;
	console = Console;
	Error = Error;
	extends = extends;
	instanceof = instanceof;
	Math = Math;
	Number = Number;
	Object = Collections.Object;
	Map = Collections.Map;
	coerceToMap = Collections.coerceToMap;
	coerceToTable = Collections.coerceToTable;
	Set = Collections.Set;
	WeakMap = Collections.WeakMap;
	String = String;
	Symbol = Symbol;

	clearInterval = Timers.clearInterval;
	clearTimeout = Timers.clearTimeout;
	encodeURIComponent = encodeURIComponent;
	setInterval = Timers.setInterval;
	setTimeout = Timers.setTimeout;
	util = table.freeze({
		encodeURIComponent = encodeURIComponent;
		inspect = Collections.inspect;
	});
})


--!optimize 2
--!strict

local Utf8 = {}

function Utf8.Graphemes(value: string, start: number?, finish: number?): () -> (number, number)
	local trueStart = start or 1
	local trueFinish = finish or #value

	local codePoints = {}
	local length = 0
	for point in utf8.codes(value) do
		if point >= trueStart and point <= trueFinish then
			length += 1
			codePoints[length] = point
		end
	end

	codePoints[length + 1] = trueFinish + 1

	local index = 1
	local function iterate(): ()
		if index > length then
			return nil
		end

		local first = codePoints[index]
		local last = codePoints[index + 1] - 1
		index += 1
		return first, last
	end

	return iterate :: never
end

return table.freeze(Utf8)


--!optimize 2
--!strict

local Utf8 = require("@self/Utf8")

local LunePolyfill = table.freeze({
	Utf8 = Utf8;
})

return LunePolyfill


--!nonstrict
--!optimize 2

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

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

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

	STATIC FIELDS:
		Option.None: Option<None>

	STATIC METHODS:
		Option.Is(obj): boolean

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

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

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

	Example:

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

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

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

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

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

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

type Matches<T = unknown, V = unknown> = {
	Some: (value: T) -> V,
	None: () -> V,
}
type VoidMatches<T = unknown> = {
	Some: (value: T) -> (),
	None: () -> (),
}

type Serialized<T = unknown> = {
	ClassName: number,
	Value: T | T?,
}

export type VoidOption = {
	IsNotNull: boolean,
	Value: any | any?,

	Match: <V>(self: VoidOption, matches: Matches<any, any>) -> any,
	FastMatch: <V>(self: VoidOption, onSome: (value: any) -> any, onNone: () -> any) -> any,
	FastMatchVoid: (self: VoidOption, onSome: (value: any) -> (), onNone: () -> ()) -> (),

	IsSome: (self: VoidOption) -> boolean,
	IsNone: (self: VoidOption) -> boolean,

	Expect: (self: VoidOption, message: string) -> any,
	ExpectNone: (self: VoidOption, message: string) -> (),

	Unwrap: (self: VoidOption) -> any,
	UnwrapOr: (self: VoidOption, defaultValue: any) -> any,
	UnwrapOrElse: (self: VoidOption, getDefault: () -> any) -> any,

	And: (self: VoidOption, otherOption: VoidOption) -> VoidOption,
	AndThen: (self: VoidOption, getOther: (value: any) -> VoidOption) -> VoidOption,
	Or: (self: VoidOption, otherOption: VoidOption) -> VoidOption,
	OrElse: (self: VoidOption, getOther: () -> VoidOption) -> VoidOption,

	AsPointer: (self: VoidOption) -> any?,
}

export type Option<T> = {
	IsNotNull: boolean,
	Value: T | T?,

	-- Match: (<V>(self: Option<T>, Matches: Matches<T, V>) -> V) & ((self: Option<T>, Matches: VoidMatches<T>) -> ()),
	-- FastMatch: (<V>(self: Option<T>, OnSome: (Value: T) -> V, OnNone: () -> V) -> V)
	-- 	& ((self: Option<T>, OnSome: (Value: T) -> (), OnNone: () -> ()) -> ()),
	Match: <V>(self: VoidOption, matches: Matches<T, V>) -> V,
	FastMatch: <V>(self: VoidOption, onSome: (value: T) -> V, onNone: () -> V) -> V,
	FastMatchVoid: (self: VoidOption, onSome: (value: T) -> (), onNone: () -> ()) -> (),

	IsSome: (self: VoidOption) -> boolean,
	IsNone: (self: VoidOption) -> boolean,

	Expect: (self: VoidOption, message: string) -> T,
	ExpectNone: (self: VoidOption, message: string) -> (),

	Unwrap: (self: VoidOption) -> T,
	UnwrapOr: (self: VoidOption, defaultValue: T) -> T,
	UnwrapOrElse: (self: VoidOption, getDefault: () -> T) -> T,

	And: (self: VoidOption, otherOption: VoidOption) -> VoidOption,
	AndThen: (self: VoidOption, getOther: (value: T) -> VoidOption) -> VoidOption,
	Or: (self: VoidOption, otherOption: VoidOption) -> VoidOption,
	OrElse: (self: VoidOption, getOther: () -> VoidOption) -> VoidOption,

	AsPointer: (self: VoidOption) -> T?,
}

local function Option_new<T>(value: T?): Option<T>
	return setmetatable({
		IsNotNull = value ~= nil;
		Value = value;
	}, Option) :: never
end

--[=[
	Creates an Option instance with the given value. Throws an error
	if the given value is `nil`.

	@function Some
	@within Option

	@param value T
	@return Option<T>
]=]
local function Option_Some<T>(value: T)
	assert(value ~= nil, "Option.Some() value cannot be nil")
	return Option_new(value)
end
Option.Some = Option_Some

--[=[
	Safely wraps the given value as an option. If the
	value is `nil`, returns `Option.None`, otherwise
	returns `Option.Some(value)`.

	@param value T
	@return Option<T> | Option<None>
]=]
function Option.Wrap<T>(value: T?): Option<T>
	return if value == nil then Option.None else Option_Some(value)
end

--[=[
	Returns `true` if `obj` is an Option.

	@function Is
	@within Option

	@param value any
	@return boolean
]=]
local function Option_Is(value)
	return type(value) == "table" and getmetatable(value) == Option
end
Option.Is = Option_Is

--[=[
	Throws an error if `obj` is not an Option.

	@function Assert
	@within Option

	@param object unknown
	return Option<T>
]=]
local function Option_Assert<T>(object: unknown): Option<T>
	assert(Option_Is(object), "Result was not of type Option")
	return object :: never
end
Option.Assert = Option_Assert

--[=[
	Matches against the option.

	```lua
	local opt = Option.Some(32)
	opt:Match {
		Some = function(num) print("Number", num) end,
		None = function() print("No value") end,
	}
	```

	@param matches {Some: (value: any) -> any, None: () -> any}
	@return any
]=]
function Option:Match(matches: Matches)
	local onSome = matches.Some
	local onNone = matches.None
	assert(type(onSome) == "function", "Missing 'Some' match")
	assert(type(onNone) == "function", "Missing 'None' match")

	if self.IsNotNull then
		return onSome(self:Expect("Cannot unwrap option of None type"))
	end

	return onNone()
end

--[=[
	Matches against the option.

	```lua
	Option.Some(32):FastMatch(function(Number)
		print("Number", Number)
	end, function()
		print("No value.")
	end)
	```

	@param onSome (value: T) -> U -- The OnSome function.
	@param onNone () -> U -- The OnNone function.
	@return U
]=]
function Option:FastMatch(onSome, onNone)
	assert(type(onSome) == "function", "Missing 'Some' match")
	assert(type(onNone) == "function", "Missing 'None' match")

	if self.IsNotNull then
		return onSome(self:Expect("Cannot unwrap option of None type"))
	end

	return onNone()
end
function Option:FastMatchVoid(onSome, onNone)
	assert(type(onSome) == "function", "Missing 'Some' match")
	assert(type(onNone) == "function", "Missing 'None' match")

	if self.IsNotNull then
		onSome(self:Expect("Cannot unwrap option of None type"))
	else
		onNone()
	end
end

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

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

--[=[
	Unwraps the value in the option, otherwise throws an error with `message` as the error message.

	```lua
	local opt = Option.Some(10)
	print(opt:Expect("No number")) -> 10
	print(Option.None:Expect("No number")) -- Throws an error "No number"
	```

	@param message string
	@return T
]=]
function Option:Expect(message: string)
	assert(self.IsNotNull, message)
	return self.Value
end

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

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

--[=[
	If the option holds a value, returns the value. Otherwise, returns `default`.
	@param default T
	@return T
]=]
function Option:UnwrapOr(default)
	return if self.IsNotNull then self:Expect("Cannot unwrap option of None type") else default
end

function Option:AsPointer()
	return self.Value
end

--[=[
	If the option holds a value, returns the value. Otherwise, returns the
	result of the `defaultFn` function.

	@param getDefault () -> T
	@return T
]=]
function Option:UnwrapOrElse(getDefault)
	if self.IsNotNull then
		return self:Expect("Cannot unwrap option of None type")
	end

	return getDefault()
end

--[=[
	Returns `OptionB` if the calling option has a value,
	otherwise returns None.

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

	local optionA = Option.None
	local OptionB = Option.Some(64)
	local opt = optionA:And(OptionB)
	-- opt == Option.None
	```

	@param otherOption Option
	@return Option
]=]
function Option:And(otherOption)
	return if self.IsNotNull then otherOption else Option.None
end

--[=[
	If the option holds a value, then the `ThenFunction`
	function is called with the held value of the option,
	and then the resultant Option returned by the `ThenFunction`
	is returned. Otherwise, None is returned.

	```lua
	local optA = Option.Some(32)
	local optB = optA:AndThen(function(num)
		return Option.Some(num * 2)
	end)

	print(optB:Expect("Expected number")) --> 64
	```

	@param thenFunction (value: T) -> Option<U>
	@return Option<U>
]=]
function Option:AndThen(thenFunction)
	return if self.IsNotNull
		then Option_Assert(thenFunction(self:Expect("Cannot unwrap option of None type")))
		else Option.None
end

--[=[
	If caller has a value, returns itself. Otherwise, returns `otherOption`.
	@param otherOption Option<T>
	@return Option<U>
]=]
function Option:Or(otherOption)
	return if self.IsNotNull then self else otherOption
end

--[=[
	If caller has a value, returns itself. Otherwise, returns the
	option generated by the `getOther` function.

	@param getOther () -> Option<T>
	@return Option<T>
]=]
function Option:OrElse(getOther)
	return if self.IsNotNull then self else Option_Assert(getOther())
end

--[=[
	If both `self` and `OptionB` have values _or_ both don't have a value,
	then this returns None. Otherwise, it returns the option that does have
	a value.

	@param otherOption Option<T>
	@return Option<T>
]=]
function Option:XOr(otherOption)
	local someSelf = self.IsNotNull
	local someOtherOption = otherOption.IsNotNull
	if someSelf == someOtherOption then
		return Option.None
	end

	if someSelf then
		return self
	end

	return otherOption
end

--[=[
	Returns `self` if this option has a value and the predicate returns `true.
	Otherwise, returns None.

	@param predicate (Value: T) -> boolean
	@return Option<T>
]=]
function Option:Filter(predicate)
	return if not self.IsNotNull or not predicate(self.Value) then Option.None else self
end

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

--[=[
	Metamethod to transform the option into a string.

	```lua
	local optA = Option.Some(64)
	local optB = Option.None
	print(optA) --> Option<number>
	print(optB) --> Option<None>
	```

	@return string
]=]
function Option:__tostring()
	if self.IsNotNull then
		local typeOf = typeof(self.Value)
		if typeOf == "table" then
			local asString = tostring(self.Value)
			if string.sub(asString, 1, 7) ~= "table: " then
				return `Option<{asString}>`
			end
		elseif typeOf == "userdata" then
			local asString = tostring(self.Value)
			if string.sub(asString, 1, 10) ~= "userdata: " then
				return `Option<{asString}>`
			end
		end

		return `Option<{typeOf}>`
	end

	return "Option<None>"
end

--[=[
	Metamethod to check equality between two options. Returns `true` if both
	options hold the same value _or_ both options are None.

	```lua
	local o1 = Option.Some(32)
	local o2 = Option.Some(32)
	local o3 = Option.Some(64)
	local o4 = Option.None
	local o5 = Option.None

	print(o1 == o2) --> true
	print(o1 == o3) --> false
	print(o1 == o4) --> false
	print(o4 == o5) --> true
	```

	@param optionB Option
	@return boolean
]=]
function Option:__eq(optionB)
	if Option_Is(optionB) then
		if self.IsNotNull and optionB.IsNotNull then
			return self:Expect("Cannot unwrap option of None type")
				== optionB:Expect("Cannot unwrap option of None type")
		end

		if not self.IsNotNull and not optionB.IsNotNull then
			return true
		end
	end

	return false
end

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

--export type Option<T = any> = typeof(Option._new())

return table.freeze(Option)


--!optimize 2
--!strict

local LuauPath = require("@packages/LuauPath")
local LuauPolyfill = require("@packages/LuauPolyfill")
local fs = require("@lune/fs")
local process = require("@lune/process")

local PathFileSystem = {}

local Path = LuauPath.Path
local console = LuauPolyfill.console

export type AsPath = LuauPath.AsPath
export type Contents = string | buffer
export type Metadata = fs.Metadata
export type MetadataKind = fs.MetadataKind
export type MetadataPermissions = fs.MetadataPermissions
export type Path = LuauPath.Path
export type WriteOptions = fs.WriteOptions

PathFileSystem.Path = Path

local function AsPathToString(asPath: AsPath): string
	if type(asPath) == "string" then
		return asPath
	end

	return Path.from(asPath):toString()
end

local function AsPathToPath(asPath: AsPath): Path
	if Path.is(asPath) then
		return asPath :: Path
	end

	return Path.from(asPath)
end

function PathFileSystem.Copy(from: AsPath, to: AsPath, overwriteOrOptions: boolean | WriteOptions | nil): ()
	return fs.copy(AsPathToString(from), AsPathToString(to), overwriteOrOptions)
end
PathFileSystem.copy = PathFileSystem.Copy

local function IsDirectory(path: AsPath): boolean
	return fs.isDir(AsPathToString(path))
end
PathFileSystem.IsDirectory = IsDirectory
PathFileSystem.isDir = IsDirectory

function PathFileSystem.IsDirectorySafe(path: AsPath): boolean
	local success, value = pcall(fs.isDir, AsPathToString(path))
	if not success then
		console.warn(`Failed to check if '{path}' is a directory - {value}`)
		return false
	end
	return value
end
function PathFileSystem.IsDirectorySafeSilent(path: AsPath): boolean
	local success, value = pcall(fs.isDir, AsPathToString(path))
	return if success then value else false
end

local function IsFile(path: AsPath): boolean
	return fs.isFile(AsPathToString(path))
end
PathFileSystem.IsFile = IsFile
PathFileSystem.isFile = IsFile
function PathFileSystem.IsFileSafe(path: AsPath): boolean
	local success, value = pcall(fs.isFile, AsPathToString(path))
	if not success then
		console.warn(`Failed to check if '{path}' is a file - {value}`)
		return false
	end
	return value
end
function PathFileSystem.IsFileSafeSilent(path: AsPath): boolean
	local success, value = pcall(fs.isFile, AsPathToString(path))
	return if success then value else value
end

local function Metadata(path: AsPath): Metadata
	return fs.metadata(AsPathToString(path))
end
PathFileSystem.Metadata = Metadata
PathFileSystem.metadata = Metadata

local function Move(from: AsPath, to: AsPath, overwriteOrOptions: boolean | WriteOptions | nil): ()
	return fs.move(AsPathToString(from), AsPathToString(to), overwriteOrOptions)
end
PathFileSystem.Move = Move
PathFileSystem.move = Move

function PathFileSystem.ReadDirectory(path: AsPath): {string}
	return fs.readDir(AsPathToString(path))
end
PathFileSystem.readDir = PathFileSystem.ReadDirectory

function PathFileSystem.ReadFile(path: AsPath): string
	return fs.readFile(AsPathToString(path))
end
PathFileSystem.readFile = PathFileSystem.ReadFile

local function RemoveDirectory(path: AsPath): ()
	return fs.removeDir(AsPathToString(path))
end
PathFileSystem.RemoveDirectory = RemoveDirectory
PathFileSystem.removeDir = RemoveDirectory

local function RemoveFile(path: AsPath): ()
	return fs.removeFile(AsPathToString(path))
end
PathFileSystem.RemoveFile = RemoveFile
PathFileSystem.removeFile = RemoveFile

local function WriteFile(path: AsPath, contents: Contents): ()
	return fs.writeFile(AsPathToString(path), contents)
end
PathFileSystem.WriteFile = WriteFile
PathFileSystem.writeFile = WriteFile

local function WriteDirectory(path: AsPath): ()
	return fs.writeDir(AsPathToString(path))
end
PathFileSystem.WriteDirectory = WriteDirectory
PathFileSystem.writeDir = WriteDirectory

-- custom

local function Exists(path: AsPath): boolean
	return Metadata(path).exists
end
PathFileSystem.Exists = Exists

function PathFileSystem.Rename(from: AsPath, to: AsPath): ()
	return if Exists(from) and not Exists(to) then pcall(Move, from, to, false) else false
end

-- function PathFileSystem.Join(...: Path.AsPath) end

function PathFileSystem.GetScriptPath(): Path
	local path = string.match(debug.info(2, "s"), "%[string \"([^\"]*)\"%]")
	if not path then
		error("Failed to get script path")
	end

	local luauPath = path .. ".luau"
	if fs.isFile(luauPath) then
		return Path.new(luauPath)
	end

	local luaPath = path .. ".lua"
	if fs.isFile(luaPath) then
		return Path.new(luaPath)
	end

	if fs.isFile(path) then
		return Path.new(path)
	end

	error("Failed to get script path")
end

local function NormalizePath(path: Path): Path
	return Path.new(path:components():asPathString())
end
PathFileSystem.NormalizePath = NormalizePath

function PathFileSystem.GetAbsolutePathOf(path: Path): Path
	return NormalizePath(PathFileSystem.CWD:join(path))
end

function PathFileSystem.Exists(path: AsPath): boolean
	return fs.isDir(AsPathToString(path)) or fs.isFile(AsPathToString(path))
end
function PathFileSystem.FastExists(path: AsPath): boolean
	return fs.metadata(AsPathToString(path)).exists
end

function PathFileSystem.GetNoCurrentDirectoryPathOf(path: Path): Path
	local components = path:components()
	local firstComponent = components:next()
	if not firstComponent then
		return path
	end

	if firstComponent.type == "curDir" then
		return Path.new(components:asPathString())
	end

	return path
end

local function GetChildren(asPath: AsPath): {Path}
	if not PathFileSystem.IsDirectory(asPath) then
		return {}
	end

	local path = AsPathToPath(asPath)
	local inDirectory = PathFileSystem.ReadDirectory(asPath)
	local children = table.create(#inDirectory)
	for index, child in inDirectory do
		children[index] = path:join(child)
	end
	return children
end
PathFileSystem.GetChildren = GetChildren

function PathFileSystem.GetDescendants(asPath: AsPath): {Path}
	local descendants = GetChildren(asPath)
	local totalDescendants = #descendants
	local length = 0

	if totalDescendants > 0 then
		repeat
			length += 1
			local grandChildren = GetChildren(descendants[length])
			for index, grandChild in grandChildren do
				descendants[totalDescendants + index] = grandChild
			end
			totalDescendants += #grandChildren
		until length == totalDescendants
	end

	return descendants
end

PathFileSystem.PathSeparator = if process.os == "windows" then "\\" else "/"
-- selene: allow(global_usage)
_G.SYS_PATH_SEPARATOR = PathFileSystem.PathSeparator

PathFileSystem.CWD = NormalizePath(Path.new(process.cwd))

local function TrimArray(array: {string}): {string}
	local newArray: {string} = array
	if array[1] == "" then
		newArray = table.clone(array)
		table.remove(newArray, 1)
	end

	local length = #array
	if array[length] == "" then
		if newArray == array then
			newArray = table.clone(array)
		end
		newArray[length] = nil
	end

	return newArray
end

local function GetPathSegments(asPath: AsPath): {Path}
	local split = TrimArray(string.split(NormalizePath(AsPathToPath(asPath)):toString(), "/"))
	local length = #split
	local segments: {Path} = table.create(length)
	for index, value in split do
		segments[index] = Path.new(value)
	end
	return segments
end
PathFileSystem.GetPathSegments = GetPathSegments

local function GetPathSegmentsEasy(asPath: AsPath): {string}
	return TrimArray(string.split(NormalizePath(AsPathToPath(asPath)):toString(), "/"))
end
PathFileSystem.GetPathSegmentsEasy = GetPathSegmentsEasy

-- local function AsArrayToPath(array: {string}): Path
-- 	local path = Path.new(array[1])
-- 	for index = 2, #array do
-- 		path = path:join(array[index])
-- 	end
-- 	return path
-- end

function PathFileSystem.WriteUfsFile(asPath: AsPath, contents: Contents): Path
	local path = AsPathToPath(asPath)
	local segments = GetPathSegmentsEasy(path)

	local length = #segments
	if length < 2 then
		WriteFile(path, contents)
		return path
	end

	for index = 1, length - 1 do
		local currentPath = Path.new(table.concat(segments, "/", 1, index))
		if not IsDirectory(currentPath) then
			WriteDirectory(currentPath)
		end
	end

	WriteFile(path, contents)
	return path
end
function PathFileSystem.UfsUnlink(...: AsPath): ()
	for index = 1, select("#", ...) do
		local path = select(index, ...)
		if IsDirectory(path) then
			RemoveDirectory(path)
		elseif IsFile(path) then
			RemoveFile(path)
		end
	end
end

return table.freeze(PathFileSystem)


--!optimize 2
--!strict

local Promise = require("@packages/Promise")

local function PackRest<S, T...>(statusOrSuccess: S, ...: T...)
	-- table.pack cringes with typed varargs, so we do this instead.
	-- selene: allow(mixed_table)
	return statusOrSuccess, {
		n = select("#", ...);
		select(1, ...);
	}
end

--[=[
	A function that turns another function into the same as an async function
	in ECMAScript. This supports multiple returns however.

	```ts
	async function promiseUpperAndLower(value: string) {
		return [value.toUpperCase(), value.toLowerCase()];
	}
	```

	would basically be equivalent to:

	```lua
	local function GetUpperAndLowerAsync(value: string)
		return string.upper(value), string.lower(value)
	end

	local PromiseUpperAndLower = PromiseUtilities.AsyncMulti(GetUpperAndLowerAsync)
	```

	:::info
	This is meant to be entirely equivalent to the TypeScript version. I would
	recommend using [PromiseUtilities.FastAsyncMulti] instead for better
	performance.
	:::

	@function AsyncMulti
	@within PromiseUtilities

	@param callback (Arguments...) -> Returns...
	@return (Arguments...) -> Promise<Returns...>
]=]
local function AsyncMulti<Arguments..., Returns...>(callback: (Arguments...) -> Returns...)
	local function asyncFunction(...: Arguments...): Promise.TypedPromise<Returns...>
		local arguments = {select(1, ...)}
		local length = select("#", ...)

		return Promise.new(function(resolve, reject)
			coroutine.wrap(function()
				local success, values = PackRest(pcall(callback, table.unpack(arguments, 1, length)))
				if success then
					resolve(table.unpack(values, 1, values.n))
				else
					reject(table.unpack(values, 1, values.n))
				end
			end)()
		end)
	end

	return asyncFunction
end

return AsyncMulti