--!optimize 2
--!strict

local GreenTea = require("./GreenTea")
local tCompat = require("./tCompat")

export type Cause = GreenTea.Cause
export type Type = GreenTea.Type
export type TuplePacked<T...> = GreenTea.TuplePacked<T...>

local export = {}

export.t = tCompat

export.isGreenTeaType = GreenTea.isGreenTeaType
export.isGtType = GreenTea.isGtType

export.any = GreenTea.any
export.unknown = GreenTea.unknown
export.never = GreenTea.never

export.boolean = GreenTea.boolean
export.bool = GreenTea.bool

export.coroutine = GreenTea.coroutine
export.thread = GreenTea.thread

export.buffer = GreenTea.buffer

export.userdata = GreenTea.userdata

export.none = GreenTea.none

export.literal = GreenTea.literal

export.withCustom = GreenTea.withCustom
export.custom = GreenTea.custom

export.number = GreenTea.number

export.string = GreenTea.string

export.isTypeof = GreenTea.isTypeof
export.isType = GreenTea.isType

export.vararg = GreenTea.vararg
export.tuple = GreenTea.tuple

export.args = GreenTea.args
export.returns = GreenTea.returns
export.fn = GreenTea.fn
export.anyfn = GreenTea.anyfn

export.tuplePacked = GreenTea.tuplePacked

export.table = GreenTea.table
export.struct = GreenTea.struct

export.anyTable = GreenTea.anyTable
export.array = GreenTea.array
export.dictionary = GreenTea.dictionary

export.union = GreenTea.union
export.oneOf = GreenTea.oneOf

export.intersection = GreenTea.intersection
export.allOf = GreenTea.allOf

export.optional = GreenTea.optional
export.opt = GreenTea.opt

export.typeof = GreenTea.typeof

export.typecast = GreenTea.typecast
export.asGreenTeaType = GreenTea.asGreenTeaType
export.asGtType = GreenTea.asGtType

export.build = GreenTea.build

export.meta = GreenTea.meta

table.freeze(export)
table.freeze(export.t)
table.freeze(GreenTea.__Cause)
table.freeze(GreenTea.__Type)

for key, value in export do
	GreenTea.__greenteaConstructorsSet[value] = `GreenTea.{key}`
end

for key: string, value: any in pairs(export.t) do
	GreenTea.__greenteaConstructorsSet[value] = `GreenTea.t.{key}`
end

return export


--!optimize 2
--!strict

local GreenTea = require("./GreenTea")

local t = {}

type Typechecker = (...any) -> (boolean, any?)

type TypecheckerConstr<T... = ()> = (T...) -> Typechecker

local function asPlainFn(fn: (...any) -> ...any): Typechecker
	return function()
		return fn()
	end
end

local function asGreenTeaType(thing: any): any
	if GreenTea.isGtType(thing) then
		return thing
	elseif typeof(thing) == "function" then
		return GreenTea.custom(thing)
	elseif thing == nil then
		return GreenTea.none()
	else
		return GreenTea.typeof(thing)
	end
end

local implicitConstructors = {
	boolean = GreenTea.boolean :: Typechecker;
	buffer = (GreenTea.buffer :: any) :: Typechecker;
	callback = (GreenTea.anyfn :: any) :: Typechecker;
	["function"] = (GreenTea.anyfn :: any) :: Typechecker;
	none = (GreenTea.none :: any) :: Typechecker;
	["nil"] = (GreenTea.none :: any) :: Typechecker;
	string = asPlainFn(GreenTea.string);
	table = asPlainFn(GreenTea.anyTable);
	userdata = asPlainFn(GreenTea.userdata);
	number = asPlainFn(GreenTea.number);
	thread = asPlainFn(GreenTea.coroutine);
	any = function()
		return GreenTea.any()
	end :: Typechecker;
	nan = function()
		return GreenTea.withCustom(GreenTea.number({nan = true}), function(item)
			return item ~= item
		end) :: any
	end :: Typechecker;
	integer = function()
		return GreenTea.number({integer = true}) :: any
	end :: Typechecker;
	numberPositive = function()
		return GreenTea.number({range = "(0, inf]"}) :: any
	end :: Typechecker;
	numberNegative = function()
		return GreenTea.number({range = "[-inf, 0)"}) :: any
	end :: Typechecker;
}

local function basicType(name: string): Typechecker
	return GreenTea.__newBasicType(name)
end

implicitConstructors.Axes = basicType("Axes")
implicitConstructors.BrickColor = basicType("BrickColor")
implicitConstructors.CatalogSearchParams = basicType("CatalogSearchParams")
implicitConstructors.CFrame = basicType("CFrame")
implicitConstructors.Color3 = basicType("Color3")
implicitConstructors.ColorSequence = basicType("ColorSequence")
implicitConstructors.ColorSequenceKeypoint = basicType("ColorSequenceKeypoint")
implicitConstructors.DateTime = basicType("DateTime")
implicitConstructors.DockWidgetPluginGuiInfo = basicType("DockWidgetPluginGuiInfo")
implicitConstructors.Enums = basicType("Enums")
implicitConstructors.Faces = basicType("Faces")
implicitConstructors.FloatCurveKey = basicType("FloatCurveKey")
implicitConstructors.Font = basicType("Font")
implicitConstructors.Instance = basicType("Instance")
implicitConstructors.NumberRange = basicType("NumberRange")
implicitConstructors.NumberSequence = basicType("NumberSequence")
implicitConstructors.NumberSequenceKeypoint = basicType("NumberSequenceKeypoint")
implicitConstructors.OverlapParams = basicType("OverlapParams")
implicitConstructors.PathWaypoint = basicType("PathWaypoint")
implicitConstructors.PhysicalProperties = basicType("PhysicalProperties")
implicitConstructors.Random = basicType("Random")
implicitConstructors.Ray = basicType("Ray")
implicitConstructors.RaycastParams = basicType("RaycastParams")
implicitConstructors.RaycastResult = basicType("RaycastResult")
implicitConstructors.RBXScriptConnection = basicType("RBXScriptConnection")
implicitConstructors.RBXScriptSignal = basicType("RBXScriptSignal")
implicitConstructors.Rect = basicType("Rect")
implicitConstructors.Region3 = basicType("Region3")
implicitConstructors.Region3int16 = basicType("Region3int16")
implicitConstructors.TweenInfo = basicType("TweenInfo")
implicitConstructors.UDim = basicType("UDim")
implicitConstructors.UDim2 = basicType("UDim2")
implicitConstructors.Vector2 = basicType("Vector2")
implicitConstructors.Vector2int16 = basicType("Vector2int16")
implicitConstructors.Vector3 = basicType("Vector3")
implicitConstructors.Vector3int16 = basicType("Vector3int16")

t.type = function(typename: string): any
	return GreenTea.isType(typename)
end :: TypecheckerConstr<string>

t.typeof = function(typename: string): any
	return GreenTea.isTypeof(typename)
end :: TypecheckerConstr<string>

t.literal = function(...: any): any
	local count = select("#", ...)
	if count == 0 then
		return GreenTea.any()
	elseif count == 1 then
		return GreenTea.literal(...)
	else
		local literals = {}
		for index = 1, count do
			table.insert(literals, GreenTea.literal(select(index, ...)))
		end

		return GreenTea.union(table.unpack(literals))
	end
end :: TypecheckerConstr<...any>

t.exactly = t.literal

t.keyOf = function(keyType: {[any]: any}): any
	local union = {}
	for key, _value in pairs(keyType) do
		table.insert(union, GreenTea.literal(key))
	end
	return GreenTea.union(table.unpack(union))
end :: TypecheckerConstr<{[any]: any}>

t.valueOf = function(valueType: {[any]: any}): any
	local union = {}
	for _key, value in pairs(valueType) do
		table.insert(union, GreenTea.typeof(value))
	end
	return GreenTea.union(table.unpack(union))
end :: TypecheckerConstr<{[any]: any}>

t.optional = function(thins: any): any
	return GreenTea.optional(asGreenTeaType(thins))
end :: TypecheckerConstr<any>

t.tuple = function(...: any): any
	local tupleArgs = table.pack(...)
	for index = 1, tupleArgs.n do
		tupleArgs[index] = asGreenTeaType(tupleArgs[index])
	end

	return GreenTea.tuple(table.unpack(tupleArgs))
end :: TypecheckerConstr<...any>

t.union = function(...: any)
	local unionArgs = table.pack(...)
	for index = 1, unionArgs.n do
		unionArgs[index] = asGreenTeaType(unionArgs[index])
	end
	return GreenTea.union(table.unpack(unionArgs))
end :: TypecheckerConstr<...any>

t.some = t.union

t.intersection = function(...: any): any
	local intersectionArgs = table.pack(...)
	for index = 1, intersectionArgs.n do
		intersectionArgs[index] = asGreenTeaType(intersectionArgs[index])
	end
	return GreenTea.intersection(table.unpack(intersectionArgs))
end :: TypecheckerConstr<...any>

t.every = t.intersection

t.keys = function(check: any): any
	return GreenTea.dictionary(asGreenTeaType(check), GreenTea.any())
end :: TypecheckerConstr<any>

t.values = function(check: any): any
	return GreenTea.dictionary(GreenTea.any(), asGreenTeaType(check))
end :: TypecheckerConstr<any>

t.map = function(key: any, value: any): any
	return GreenTea.dictionary(asGreenTeaType(key), asGreenTeaType(value))
end :: TypecheckerConstr<any, any>

t.set = function(key: any): any
	return GreenTea.dictionary(asGreenTeaType(key), GreenTea.literal(true))
end :: TypecheckerConstr<any>

t.numberMin = function(min: number): any
	return GreenTea.number({range = `[{min}, inf]`})
end :: TypecheckerConstr<number>
t.numberMax = function(max: number): any
	return GreenTea.number({range = `[-inf, {max}]`})
end :: TypecheckerConstr<number>
t.numberConstrained = function(min: number, max: number): any
	return GreenTea.number({range = `[{min}, {max}]`})
end :: TypecheckerConstr<number, number>
t.numberMinExclusive = function(min: number): any
	return GreenTea.number({range = `({min}, inf]`})
end :: TypecheckerConstr<number>
t.numberMaxExclusive = function(max: number): any
	return GreenTea.number({range = `[-inf, {max})`})
end :: TypecheckerConstr<number>
t.numberConstrainedExclusive = function(min: number, max: number): any
	return GreenTea.number({range = `({min}, {max})`})
end :: TypecheckerConstr<number, number>

t.match = function(pattern: string): any
	return GreenTea.string({pattern = pattern})
end :: TypecheckerConstr<string>

t.array = function(typechecker): any
	return GreenTea.array(asGreenTeaType(typechecker))
end :: TypecheckerConstr<any>

t.strictArray = function(...: any): any
	local arrayTypes = {...}
	arrayTypes[GreenTea.any()] = GreenTea.never()

	local count = select("#", ...)
	for index = 1, count do
		arrayTypes[index] = asGreenTeaType(arrayTypes[index])
	end

	return GreenTea.table(arrayTypes, {raw = true, count = {min = 0, max = count}})
end :: TypecheckerConstr<...any>

t.interface = function(interface: {[any]: any}): any
	local interfaceCleaned = {}
	for key, value in pairs(interface) do
		interfaceCleaned[key] = asGreenTeaType(value)
	end

	return GreenTea.table(interfaceCleaned, {raw = true})
end :: TypecheckerConstr<{[any]: any}>

t.strictInterface = function(interface: {[string]: any}): any
	local interfaceCleaned = {}
	for key, value in pairs(interface) do
		interfaceCleaned[key] = asGreenTeaType(value)
	end

	interfaceCleaned[GreenTea.any()] = GreenTea.never()

	return GreenTea.table(interfaceCleaned, {raw = true})
end :: TypecheckerConstr<{[string]: any}>

t.wrap = function<Args..., Returns...>(fn: (Args...) -> Returns..., argsCheck: any): (Args...) -> Returns...
	local argsChecker: GreenTea.Type = asGreenTeaType(argsCheck)
	return function(...: any)
		argsChecker:assert(...)

		return fn(...)
	end
end

t.strict = function<T>(typechecker: T): T
	local newTypechecker = asGreenTeaType(typechecker)
	return function(...)
		newTypechecker:assert(...)
	end :: any
end

type exportedTypes = typeof(t) & typeof(implicitConstructors)

local exportedRuntime = setmetatable(t :: any, {
	__index = function(_self, key)
		if implicitConstructors[key] then
			return implicitConstructors[key]()
		else
			return nil
		end
	end;
})

return exportedRuntime :: exportedTypes


--!optimize 2
--!strict

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

local function EasyLiteral<T>(...: any)
	local count = select("#", ...)
	local array = table.create(count)

	for index = 1, count do
		local value = select(index, ...)
		array[index] = GreenTea.literal(value)
	end

	return GreenTea.union(table.unpack(array)) :: T
end

return EasyLiteral


--!optimize 2
--!strict

local EasyLiteral = require("./EasyLiteral")
local IntersectionPlus = require("./IntersectionPlus")
local UnionPlus = require("./UnionPlus")

local GreenTeaUtilities = table.freeze({
	EasyLiteral = EasyLiteral;
	Intersection = IntersectionPlus;
	IntersectionPlus = IntersectionPlus;
	Union = UnionPlus;
	UnionPlus = UnionPlus;
})

return GreenTeaUtilities


local setType = require("./set/type")

export type Set<T> = setType.Set<T>

local Disk = {
	Array = {
		all = require("./array/all");
		alternate = require("./array/alternate");
		any = require("./array/any");
		average = require("./array/average");
		averageBy = require("./array/averageBy");
		concat = require("./array/concat");
		contains = require("./array/contains");
		copy = require("./array/copy");
		deduplicate = require("./array/deduplicate");
		deduplicateByKey = require("./array/deduplicateByKey");
		filter = require("./array/filter");
		find = require("./array/find");
		findIndex = require("./array/findIndex");
		findIndexByValue = require("./array/findIndexByValue");
		findMap = require("./array/findMap");
		flatMap = require("./array/flatMap");
		flatten = require("./array/flatten");
		fromFn = require("./array/fromFn");
		fromTryFn = require("./array/fromTryFn");
		isArray = require("./array/isArray");
		isEmpty = require("./array/isEmpty");
		map = require("./array/map");
		maximum = require("./array/maximum");
		maximumBy = require("./array/maximumBy");
		minimum = require("./array/minimum");
		minimumBy = require("./array/minimumBy");
		partition = require("./array/partition");
		pop = require("./array/pop");
		popFirst = require("./array/popFirst");
		product = require("./array/product");
		productBy = require("./array/productBy");
		push = require("./array/push");
		range = require("./array/range");
		reduce = require("./array/reduce");
		removeIndexes = require("./array/removeIndexes");
		removeSortedIndexes = require("./array/removeSortedIndexes");
		removeValues = require("./array/removeValues");
		reverse = require("./array/reverse");
		reversed = require("./array/reverse");
		sort = require("./array/sort");
		sortByKey = require("./array/sortByKey");
		stepBy = require("./array/stepBy");
		sum = require("./array/sum");
		sumBy = require("./array/sumBy");
		takeWhile = require("./array/takeWhile");
		zip = require("./array/zip");
	};
	Map = {
		copy = require("./map/copy");
		count = require("./map/count");
		entries = require("./map/entries");
		invert = require("./map/invert");
		isEmpty = require("./map/isEmpty");
		keys = require("./map/keys");
		mapEntries = require("./map/mapEntries");
		mapValues = require("./map/mapValues");
		merge = require("./map/merge");
		remove = require("./map/remove");
		values = require("./map/values");
	};
	Set = {
		fromArray = require("./set/fromArray");
	};
	None = require("./None");
	WeakMap = require("./WeakMap");
}

return Disk


--!nolint LocalShadow
--!optimize 2

export type PrefixEnum =
	{type: "Verbatim", value: string}
	| {type: "VerbatimUNC", hostName: string, shareName: string}
	| {type: "VerbatimDisk", value: string}
	| {type: "DeviceNS", value: string}
	| {type: "UNC", hostName: string, shareName: string}
	| {type: "Disk", value: string}

export type Prefix = PrefixEnum & {
	len: (self: Prefix) -> number,
	isVerbatim: (self: Prefix) -> boolean,
	isDrive: (self: Prefix) -> boolean,
	hasImplicitRoot: (self: Prefix) -> boolean,

	clone: (self: Prefix) -> Prefix,
	equal: (self: Prefix, other: Prefix) -> boolean,
}

type PrefixStatic = Prefix & {
	Verbatim: (value: string) -> Prefix,
	VerbatimUNC: (hostName: string, shareName: string) -> Prefix,
	VerbatimDisk: (value: string) -> Prefix,
	DeviceNS: (value: string) -> Prefix,
	UNC: (hostName: string, shareName: string) -> Prefix,
	Disk: (value: string) -> Prefix,
}

local Prefix: PrefixStatic = {} :: any
local PrefixMetatable = {
	__index = Prefix;
	__eq = function(self: Prefix, other: Prefix): boolean
		return self:equal(other)
	end;
}

local function new(self: PrefixEnum)
	return setmetatable(self, PrefixMetatable) :: any
end

function Prefix.Verbatim(value: string): Prefix
	return new({type = "Verbatim", value = value})
end

function Prefix.VerbatimUNC(hostName: string, shareName: string): Prefix
	return new({type = "VerbatimUNC", hostName = hostName, shareName = shareName})
end

function Prefix.VerbatimDisk(value: string): Prefix
	return new({type = "VerbatimDisk", value = value})
end

function Prefix.DeviceNS(value: string): Prefix
	return new({type = "DeviceNS", value = value})
end

function Prefix.UNC(hostName: string, shareName: string): Prefix
	return new({type = "UNC", hostName = hostName, shareName = shareName})
end

function Prefix.Disk(value: string): Prefix
	return new({type = "Disk", value = value})
end

function Prefix:len(): number
	local selfType = self.type
	if selfType == "Verbatim" then
		return 4 + string.len((self :: any).value)
	elseif selfType == "VerbatimUNC" then
		local data: {hostName: string, shareName: string} = self :: any
		local shareNameLen = string.len(data.shareName)
		return 8 + string.len(data.hostName) + if shareNameLen > 0 then 1 + shareNameLen else 0
	elseif selfType == "VerbatimDisk" then
		return 6
	elseif selfType == "UNC" then
		local data: {hostName: string, shareName: string} = self :: any
		local shareNameLen = string.len(data.shareName)
		return 2 + string.len(data.hostName) + if shareNameLen > 0 then 1 + shareNameLen else 0
	elseif selfType == "DeviceNS" then
		return 4 + string.len((self :: any).value)
	elseif selfType == "Disk" then
		return 2
	elseif _G.DEV then
		error("unreachable")
	end
	return 0
end

function Prefix:isVerbatim(): boolean
	local selfType = self.type
	return selfType == "Verbatim" or selfType == "VerbatimDisk" or selfType == "VerbatimUNC"
end

function Prefix:isDrive(): boolean
	return self.type == "Disk"
end

function Prefix:hasImplicitRoot(): boolean
	return self.type ~= "Disk"
end

function Prefix:clone(): Prefix
	local self = table.clone(self)

	return setmetatable(self, PrefixMetatable) :: any
end

function Prefix:equal(other: Prefix): boolean
	local selfType = self.type

	if selfType ~= other.type then
		return false
	end

	if selfType == "Verbatim" then
		return (self :: any).value == (other :: any).value
	end
	if selfType == "VerbatimUNC" then
		return (self :: any).hostName == (other :: any).hostName and (self :: any).shareName == (other :: any).shareName
	end
	if selfType == "VerbatimDisk" then
		return (self :: any).value == (other :: any).value
	end
	if selfType == "DeviceNS" then
		return (self :: any).value == (other :: any).value
	end
	if selfType == "UNC" then
		return (self :: any).hostName == (other :: any).hostName and (self :: any).shareName == (other :: any).shareName
	end
	if selfType == "Disk" then
		return (self :: any).value == (other :: any).value
	end

	return true
end

return Prefix


--!nolint LocalShadow
--!optimize 2

local Disk = require("@packages/LuauDisk")

local Array = Disk.Array

export type Rev<T> = {
	next: (self: Rev<T>) -> T?,
	collect: (self: Rev<T>) -> {T},
	rev: (self: Rev<T>) -> Rev<T>,
	-- DoubleEndedIterator
	nextBack: (self: Rev<T>) -> T?,

	clone: (self: Rev<T>) -> Rev<T>,
}

type DoubleEndedIterator<T> = {
	next: (self: DoubleEndedIterator<T>) -> T?,
	nextBack: (self: DoubleEndedIterator<T>) -> T?,

	clone: (self: DoubleEndedIterator<T>) -> DoubleEndedIterator<T>,
}

type Private<T> = {
	_iter: DoubleEndedIterator<T>,
}
type PrivateRev<T> = Rev<T> & Private<T>

type RevStatic = {
	new: <T>(iter: DoubleEndedIterator<T>) -> Rev<T>,

	next: <T>(self: Rev<T>) -> T?,
	collect: <T>(self: Rev<T>) -> {T},
	rev: <T>(self: Rev<T>) -> Rev<T>,
	-- DoubleEndedIterator
	nextBack: <T>(self: Rev<T>) -> T?,

	clone: <T>(self: Rev<T>) -> Rev<T>,
}

local Rev: RevStatic = {} :: any
local RevMetatable = {
	__index = Rev;
	__iter = function<T>(self: PrivateRev<T>)
		local items = self:collect()
		return next, items
	end;
}

function Rev.new<T>(iter: DoubleEndedIterator<T>): Rev<T>
	local self: Private<T> = {
		_iter = iter;
	}

	return setmetatable(self, RevMetatable) :: any
end

function Rev:next<T>(): T?
	local self: PrivateRev<T> = self :: any

	return self._iter:nextBack()
end

function Rev:collect<T>(): {T}
	local self: PrivateRev<T> = self :: any

	return Array.fromFn(function()
		return self:next()
	end)
end

function Rev:nextBack<T>(): T?
	local self: PrivateRev<T> = self :: any

	return self._iter:next()
end

function Rev:clone<T>(): Rev<T>
	local self: PrivateRev<T> = self :: any

	return Rev.new(self._iter:clone())
end

return Rev


--!optimize 2

local Path = require("./Path")

type PrefixEnum =
	{type: "Verbatim", value: string}
	| {type: "VerbatimUNC", hostName: string, shareName: string}
	| {type: "VerbatimDisk", value: string}
	| {type: "DeviceNS", value: string}
	| {type: "UNC", hostName: string, shareName: string}
	| {type: "Disk", value: string}
type Prefix = PrefixEnum & {
	len: (self: Prefix) -> number,
	isVerbatim: (self: Prefix) -> boolean,
	isDrive: (self: Prefix) -> boolean,
	hasImplicitRoot: (self: Prefix) -> boolean,

	clone: (self: Prefix) -> Prefix,
	equal: (self: Prefix, other: Prefix) -> boolean,
}

type PrefixComponent = {
	type: "prefix",
	raw: string,
	parsed: Prefix,
}
type NormalComponent = {type: "normal", value: string}
type ComponentEnum = NormalComponent | {type: "rootDir"} | {type: "curDir"} | {type: "parentDir"} | PrefixComponent
type ComponentCommon = {
	toString: (self: Component) -> string,
	equal: (self: Component, other: Component) -> boolean,
}
type Component = ComponentEnum & ComponentCommon
type Rev<T> = {
	next: (self: Rev<T>) -> T?,
	collect: (self: Rev<T>) -> {T},
	rev: (self: Rev<T>) -> Rev<T>,
	-- DoubleEndedIterator
	nextBack: (self: Rev<T>) -> T?,

	clone: (self: Rev<T>) -> Rev<T>,
}

type Components = {
	-- The prefix as it was originally parsed, if any
	prefix: Prefix?,

	prefixLen: (self: Components) -> number,
	prefixVerbatim: (self: Components) -> boolean,
	-- how much of the prefix is left from the point of view of iteration?
	prefixRemaining: (self: Components) -> number,
	-- Given the iteration so far, how much of the pre-State::Body path is left?
	lenBeforeBody: (self: Components) -> number,
	-- is the iteration complete?
	finished: (self: Components) -> boolean,
	isSepByte: (self: Components, b: string) -> boolean,
	-- asPath: (self: Components) -> Path,
	asPathString: (self: Components) -> string,
	hasRoot: (self: Components) -> boolean,
	includeCurDir: (self: Components) -> boolean,
	parseSingleComponent: (self: Components, comp: string) -> Component?,
	parseNextComponent: (self: Components) -> (number, Component?),
	parseNextComponentBack: (self: Components) -> (number, Component?),
	trimLeft: (self: Components) -> (),
	trimRight: (self: Components) -> (),

	-- Iterator
	next: (self: Components) -> Component?,
	collect: (self: Components) -> {Component},
	rev: (self: Components) -> Rev<Component>,
	-- DoubleEndedIterator
	nextBack: (self: Components) -> Component?,

	-- Clone
	clone: (self: Components) -> Components,
	equal: (self: Components, other: Components) -> boolean,
}

export type AsPath = string | Path | Components

type Ancestors = {
	-- Iterator
	next: (self: Ancestors) -> Path?,
	collect: (self: Ancestors) -> {Path},
}
export type Path = {
	toString: (self: Path) -> string,

	toPathBuf: (self: Path) -> Path,
	isAbsolute: (self: Path) -> boolean,
	isRelative: (self: Path) -> boolean,
	hasRoot: (self: Path) -> boolean,
	parent: (self: Path) -> Path?,
	ancestors: (self: Path) -> Ancestors,
	fileName: (self: Path) -> string?,
	stripPrefix: (self: Path, base: AsPath) -> Path?,
	startsWith: (self: Path, base: AsPath) -> boolean,
	endsWith: (self: Path, child: AsPath) -> boolean,

	fileStem: (self: Path) -> string?,
	filePrefix: (self: Path) -> string?,
	extension: (self: Path) -> string?,
	join: (self: Path, path: AsPath) -> Path,

	withFileName: (self: Path, fileName: string) -> Path,
	withExtension: (self: Path, extension: string) -> Path,
	components: (self: Path) -> Components,

	equal: (self: Path, other: Path) -> boolean,

	-- PathBuf
	push: (self: Path, path: AsPath) -> (),
	pop: (self: Path) -> boolean,
	setFileName: (self: Path, fileName: string) -> (),
	setExtension: (self: Path, extension: string) -> boolean,
	clear: (self: Path) -> (),
}

return {
	Path = (Path :: never) :: {
		new: (path: string) -> Path,
		from: (path: AsPath) -> Path,
		is: (value: unknown) -> boolean,
	};
}


--!nolint LocalShadow
--!optimize 2

local PrefixParserSlice = require("./PrefixParserSlice")

type PrefixParserSlice = PrefixParserSlice.PrefixParserSlice

export type PrefixParser = {
	asSlice: (self: PrefixParser) -> PrefixParserSlice,
}

type Private = {
	_path: string,
	_prefix: string,
	_length: number,
}
type PrivatePrefixParser = PrefixParser & Private

type PrefixParserStatic = PrefixParser & Private & {
	new: (path: string, length: number) -> PrefixParser,
	getPrefix: (path: string, length: number) -> string,
}

local PrefixParser: PrefixParserStatic = {} :: any
local PrefixParserMetatable = {
	__index = PrefixParser;
}

function PrefixParser.new(path: string, length: number): PrefixParser
	local self: Private = {
		_path = path;
		_prefix = PrefixParser.getPrefix(path, length);
		_length = length;
	}

	return setmetatable(self, PrefixParserMetatable) :: any
end

function PrefixParser.getPrefix(path: string, length: number): string
	local prefix = string.gsub(string.sub(path, 1, length), "/", "\\")
	return prefix
end

function PrefixParser:asSlice(): PrefixParserSlice
	local self: PrivatePrefixParser = self :: any

	return PrefixParserSlice.new(self._path, string.sub(self._prefix, 1, math.min(#self._path, 8)))
end

return PrefixParser


--!nolint LocalShadow
--!optimize 2

export type PrefixParserSlice = {
	stripPrefix: (self: PrefixParserSlice, prefix: string) -> PrefixParserSlice?,
	prefixBytes: (self: PrefixParserSlice) -> string,
	finish: (self: PrefixParserSlice) -> string,
}

type Private = {
	_path: string,
	_prefix: string,
	_index: number,
}
type PrivatePrefixParserSlice = PrefixParserSlice & Private

type PrefixParserSliceStatic = PrefixParserSlice & Private & {
	new: (path: string, prefix: string) -> PrefixParserSlice,
}

local PrefixParserSlice: PrefixParserSliceStatic = {} :: any
local PrefixParserSliceMetatable = {
	__index = PrefixParserSlice;
}

function PrefixParserSlice.new(path: string, prefix: string): PrefixParserSlice
	local self: Private = {
		_path = path;
		_prefix = prefix;
		_index = 1;
	}

	return setmetatable(self, PrefixParserSliceMetatable) :: any
end

function PrefixParserSlice:stripPrefix(prefix: string): PrefixParserSlice?
	local self: PrivatePrefixParserSlice = self :: any

	local prefixLength = #prefix
	if string.sub(self._prefix, self._index, self._index + prefixLength - 1) == prefix then
		local self: Private = {
			_path = self._path;
			_prefix = self._prefix;
			_index = self._index + prefixLength;
		}

		return setmetatable(self, PrefixParserSliceMetatable) :: any
	end
	return nil
end

function PrefixParserSlice:prefixBytes(): string
	local self: PrivatePrefixParserSlice = self :: any

	return string.sub(self._path, 1, self._index)
end

function PrefixParserSlice:finish(): string
	local self: PrivatePrefixParserSlice = self :: any

	return string.sub(self._path, self._index)
end

return PrefixParserSlice


--!nolint LocalShadow
--!optimize 2

local Prefix = require("../../Prefix")
local parsePrefixWindows = require("./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" 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 {
	MAIN_SEPARATOR_STR = MAIN_SEPARATOR_STR;
	isSepByte = isSepByte;
	isVerbatimSep = isVerbatimSep;
	parsePrefix = parsePrefix;
}


--!native
--!optimize 2
--!strict

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


--!native
--!optimize 2
--!strict
return {
	toJSBoolean = require("./toJSBoolean");
}


--!native
--!optimize 2
--!strict
local Number = require("../Number")

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


--!native
--!optimize 2
--!strict

local isArray = require("./isArray")
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict

local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
local isArray = require("./isArray")
local types = require("../../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


--!native
--!optimize 2
local flat = require("./flat")
local map = require("./map")
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../../ES7Types")
type Object = types.Object
type Array<T> = types.Array<T>
type mapFn<T, U> = (element: T, index: number) -> U
type mapFnWithThisArg<T, U> = (thisArg: any, element: T, index: number) -> U

return function<T, U>(
	value: Array<T>,
	mapFn: (mapFn<T, U> | mapFnWithThisArg<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 mapFn then
		local arrayLength = #(value :: Array<T>)
		array = table.create(arrayLength)
		for i = 1, arrayLength do
			if thisArg ~= nil then
				(array :: Array<U>)[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, (value :: Array<T>)[i], i)
			else
				(array :: Array<U>)[i] = (mapFn :: mapFn<T, U>)((value :: Array<T>)[i], i)
			end
		end
	else
		array = table.clone(value :: Array<T>)
	end

	return array
end


--!native
--!optimize 2
--!strict
local types = require("../../../ES7Types")
type Object = types.Object
type Array<T> = types.Array<T>
type Map<K, V> = types.Map<K, V>
type mapFn<T, U> = (element: T, index: number) -> U
type mapFnWithThisArg<T, U> = (thisArg: any, element: T, index: number) -> U

return function<T, U>(
	value: Map<any, any>,
	mapFn: (mapFn<T, U> | mapFnWithThisArg<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 mapFn then
		array = {}
		for i, v in value :: any do
			if thisArg ~= nil then
				(array :: Array<U>)[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, v, i)
			else
				(array :: Array<U>)[i] = (mapFn :: mapFn<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


--!native
--!optimize 2
--!strict
local types = require("../../../ES7Types")
type Object = types.Object
type Array<T> = types.Array<T>
type Set<T> = types.Set<T>
type mapFn<T, U> = (element: T, index: number) -> U
type mapFnWithThisArg<T, U> = (thisArg: any, element: T, index: number) -> U

return function<T, U>(
	value: Set<T>,
	mapFn: (mapFn<T, U> | mapFnWithThisArg<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 mapFn then
		array = {}
		for i, v in value :: any do
			if thisArg ~= nil then
				(array :: Array<U>)[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, v, i)
			else
				(array :: Array<U>)[i] = (mapFn :: mapFn<T, U>)(v, i)
			end
		end
	else
		array = table.clone((value :: any)._array)
	end

	return array
end


--!native
--!optimize 2
--!strict
local types = require("../../../ES7Types")
type Object = types.Object
type Array<T> = types.Array<T>

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

return function<T, U>(
	value: string,
	mapFn: (mapFn<T, U> | mapFnWithThisArg<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 = {}

	local valueStringLength = #value
	array = table.create(valueStringLength)
	if mapFn then
		for i = 1, valueStringLength do
			if thisArg ~= nil then
				(array :: Array<U>)[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, string.sub(value, i, i) :: any, i)
			else
				(array :: Array<U>)[i] = (mapFn :: mapFn<T, U>)(string.sub(value, i, i) :: any, i)
			end
		end
	else
		for i = 1, valueStringLength do
			(array :: Array<string>)[i] = string.sub(value, i, i)
		end
	end

	return array
end


--!native
--!optimize 2
--!strict

local Map = require("../../Map/Map")
local Set = require("../../Set")
local fromArray = require("./fromArray")
local fromMap = require("./fromMap")
local fromSet = require("./fromSet")
local fromString = require("./fromString")
local instanceof = require("../../../InstanceOf")
local isArray = require("../isArray")
local types = require("../../../ES7Types")

type Array<T> = types.Array<T>
type Object = types.Object
type Set<T> = types.Set<T>
type Map<K, V> = types.Map<K, V>
type mapFn<T, U> = (element: T, index: number) -> U
type mapFnWithThisArg<T, U> = (thisArg: any, element: T, index: number) -> U

return function<T, U>(
	value: string | Array<T> | Set<T> | Map<any, any>,
	mapFn: (mapFn<T, U> | mapFnWithThisArg<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 == "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)
	elseif valueType == "string" then
		array = fromString(value :: string, mapFn, thisArg)
	else
		array = {}
	end

	return array
end


--!native
--!optimize 2
--!strict
local indexOf = require("./indexOf")
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict
local ES7Types = require("../../ES7Types")

export type Array<T> = ES7Types.Array<T>

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


--!native
--!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


--!native
--!optimize 2
--!strict
local map = require("./map")
local types = require("../../ES7Types")

type Array<T> = types.Array<T>

return function<T>(arr: Array<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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict
-- https://programming-idioms.org/idiom/19/reverse-a-list/1314/lua
local types = require("../../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


--!native
--!optimize 2
--!strict
local isArray = require("./isArray")
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict
local None = require("../Object/None")
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict
local isArray = require("./isArray")
local types = require("../../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


--!native
--!optimize 2
--!strict
local arrayForEach = require("../Array/forEach")
local arrayMap = require("../Array/map")
local instanceof = require("../../InstanceOf")
local isArray = require("../Array/isArray")
local types = require("../../ES7Types")

local __DEV__ = _G.__DEV__

type Object = types.Object
type Array<T> = types.Array<T>
type Table<T, V> = types.Table<T, V>
type Tuple<T, V> = types.Tuple<T, V>
type mapCallbackFn<K, V> = types.mapCallbackFn<K, V>
type mapCallbackFnWithThisArg<K, V> = types.mapCallbackFnWithThisArg<K, V>
type Map<K, V> = types.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


--!native
--!optimize 2
local Map = require("./Map")
local Object = require("../Object")
local instanceOf = require("../../InstanceOf")
local types = require("../../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


--!native
--!optimize 2
local Map = require("./Map")
local arrayReduce = require("../Array/reduce")
local instanceOf = require("../../InstanceOf")
local types = require("../../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


--!native
--!optimize 2
local ES7Types = require("../../ES7Types")
local Map = require("./Map")
local coerceToMap = require("./coerceToMap")
local coerceToTable = require("./coerceToTable")

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

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


--!native
--!optimize 2
--!nonstrict
-- Marker used to specify that the value is nothing, because nil cannot be
-- stored in tables.
local None = newproxy(true)
local mt = getmetatable(None)
mt.__tostring = function()
	return "Object.None"
end

return None


--!native
--!optimize 2
--!strict
local None = require("./None")
local types = require("../../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


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!strict

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


--!native
--!optimize 2
-- 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


--!native
--!optimize 2
--!strict
local __DEV__ = _G.__DEV__

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

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


--!native
--!optimize 2
local Set = require("../Set")
local instanceOf = require("../../InstanceOf")
local types = require("../../ES7Types")
type Array<T> = types.Array<T>
type Set<T> = types.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] = `{i}`
		end
	end

	return keys
end


--!native
--!optimize 2
--!strict
local types = require("../../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}`, `{key}`), 2)
			end;
			__metatable = false;
		}) :: any
	) :: T
end

return preventExtensions


--!native
--!optimize 2
--!strict
local types = require("../../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


--!native
--!optimize 2
--!nonstrict

local arrayForEach = require("./Array/forEach")
local arrayFromString = require("./Array/from/fromString")
local inspect = require("./inspect")
local isArray = require("./Array/isArray")
local types = require("../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


--!native
--!optimize 2
--!strict
local ES7Types = require("../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


--!native
--!optimize 2
--!strict

local Array = require("./Array")
local Map = require("./Map")
local Object = require("./Object")
local Set = require("./Set")
local WeakMap = require("./WeakMap")
local inspect = require("./inspect")
local types = require("../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 {
	Array = Array;
	Object = Object;
	Map = Map.Map;
	coerceToMap = Map.coerceToMap;
	coerceToTable = Map.coerceToTable;
	Set = Set;
	WeakMap = WeakMap;
	inspect = inspect;
}


--!native
--!optimize 2
--!strict
local makeConsoleImpl = require("./makeConsoleImpl")

return makeConsoleImpl()


--!native
--!optimize 2
--!strict

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

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

local INDENT = "  "

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

local function Concat(...)
	local length = select("#", ...)
	if length == 0 then
		return ""
	end

	if length == 1 then
		return tostring(...)
	end

	if length == 2 then
		local a, b = ...
		return tostring(a) .. " " .. tostring(b)
	end

	if length == 3 then
		local a, b, c = ...
		return tostring(a) .. " " .. tostring(b) .. tostring(c)
	end

	local array = table.create(length)
	for index = 1, length do
		array[index] = tostring(select(index, ...))
	end
	return table.concat(array, " ")
end

local function Warn(...)
	warn(YELLOW(Concat(...)))
end

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

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

	function console.log(content, ...)
		local message
		if type(content) == "string" then
			message = string.format(content, ...)
		else
			message = inspect(content)
		end
		print(indent() .. message)
	end

	function console.debug(content, ...)
		local message
		if type(content) == "string" then
			message = string.format(content, ...)
		else
			message = inspect(content)
		end
		print(indent() .. message)
	end

	function console.info(content, ...)
		local message
		if type(content) == "string" then
			message = string.format(content, ...)
		else
			message = inspect(content)
		end
		print(indent() .. message)
	end

	function console.warn(content, ...)
		local message
		if type(content) == "string" then
			message = string.format(content, ...)
		else
			message = inspect(content)
		end
		Warn(indent() .. message)
	end

	function console.error(content, ...)
		-- JS' `console.error` doesn't interrupt execution like Lua's `error`,
		-- which is more similar to throwing an exception in JS.
		local message
		if type(content) == "string" then
			message = string.format(content, ...)
		else
			message = inspect(content)
		end
		Warn(indent() .. message)
	end

	function console.group(content, ...)
		local message
		if type(content) == "string" then
			message = string.format(content, ...)
		else
			message = inspect(content)
		end
		print(indent() .. message)
		indentDepth += 1
	end

	function console.groupCollapsed(content, ...)
		-- There's no smart console, so this is equivalent to `console.group`
		local message
		if type(content) == "string" then
			message = string.format(content, ...)
		else
			message = inspect(content)
		end
		print(indent() .. message)
		indentDepth += 1
	end

	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 console
end


--!native
--!optimize 2
--!strict

export type Object = {[string]: any}
export type Array<T> = {[number]: T}
export type Function = (...any) -> ...any
export type Table<T, V> = {[T]: V}
export type Tuple<T, V> = Array<T | V>
export type mapCallbackFn<K, V> = (element: V, key: K, map: Map<K, V>) -> ()
export type mapCallbackFnWithThisArg<K, V> = (thisArg: Object, value: V, key: K, map: Map<K, V>) -> ()

export type Map<K, V> = typeof(setmetatable(
	{} :: {
		size: number,
		-- method definitions
		set: (self: Map<K, V>, K, V) -> Map<K, V>,
		get: (self: Map<K, V>, K) -> V | nil,
		clear: (self: Map<K, V>) -> (),
		delete: (self: Map<K, V>, K) -> boolean,
		forEach: (
			self: Map<K, V>,
			callback: mapCallbackFn<K, V> | mapCallbackFnWithThisArg<K, V>,
			thisArg: Object?
		) -> (),
		[K]: V,
		has: (self: Map<K, V>, K) -> boolean,
		keys: (self: Map<K, V>) -> Array<K>,
		values: (self: Map<K, V>) -> Array<V>,
		entries: (self: Map<K, V>) -> Array<Tuple<K, V>>,
		ipairs: (self: Map<K, V>) -> any,
		_map: {[K]: V},
		_array: {[number]: K},
		__index: (self: Map<K, V>, key: K) -> V,
		__iter: (self: Map<K, V>) -> (<K, V>({[K]: V}, K?) -> (K?, V), V),
		__newindex: (self: Map<K, V>, key: K, value: V) -> (),
	},
	{} :: {
		__index: Map<K, V>,
		__iter: (self: Map<K, V>) -> (<K, V>({[K]: V}, K?) -> (K, V), V),
		__newindex: (self: Map<K, V>, key: K, value: V) -> (),
	}
))

export type WeakMap<K, V> = {
	-- method definitions
	get: (self: WeakMap<K, V>, K) -> V,
	set: (self: WeakMap<K, V>, K, V) -> WeakMap<K, V>,
	has: (self: WeakMap<K, V>, K) -> boolean,
}

export type setCallbackFn<T> = (value: T, key: T, set: Set<T>) -> ()
export type setCallbackFnWithThisArg<T> = (thisArg: Object, value: T, key: T, set: Set<T>) -> ()

export type Set<T> = typeof(setmetatable(
	{} :: {
		size: number,
		-- method definitions
		add: (self: Set<T>, T) -> Set<T>,
		clear: (self: Set<T>) -> (),
		delete: (self: Set<T>, T) -> boolean,
		forEach: (self: Set<T>, callback: setCallbackFn<T> | setCallbackFnWithThisArg<T>, thisArg: Object?) -> (),
		has: (self: Set<T>, T) -> boolean,
		ipairs: (self: Set<T>) -> any,
	},
	{} :: {
		__index: Set<T>,
		__iter: (self: Set<T>) -> (<K, V>({[K]: V}, K?) -> (K, V), T),
	}
))
return {}


--!optimize 2
--!strict

local types = require("./ES7Types")
type Function = types.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("%*: %*", tostring(name), tostring(message))
			end

			return tostring(name)
		end

		return tostring(DEFAULT_NAME)
	end;
})


--!native
--!optimize 2
--!strict
-- polyfill for https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
local __DEV__ = _G.__DEV__

-- FIXME Luau: typing class as Object gives: Type '{ @metatable {| __call: <a>(a, ...any) -> Error, __tostring: <b, c>({+ message: b, name: c +}) -> string |}, Error }' could not be converted into 'table'
local function instanceof(tbl: any, class: any): boolean
	if __DEV__ then
		assert(type(class) == "table", "Received a non-table as the second argument for instanceof")
	end

	if type(tbl) ~= "table" then
		return false
	end

	if type(class) == "table" and type(class.instanceof) == "function" then
		return class.instanceof(tbl)
	end

	local ok, hasNew = pcall(function()
		return class.new ~= nil and tbl.new == class.new
	end)

	if ok and hasNew then
		return true
	end

	local seen = {[tbl] = true}

	while tbl and type(tbl) == "table" do
		tbl = getmetatable(tbl)
		if type(tbl) == "table" then
			tbl = tbl.__index

			if tbl == class then
				return true
			end
		end

		-- if we still have a valid table then check against seen
		if type(tbl) == "table" then
			if seen[tbl] then
				return false
			end

			seen[tbl] = true
		end
	end

	return false
end

return instanceof


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


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


--!native
--!optimize 2
--!strict
return {
	isFinite = require("./isFinite");
	isInteger = require("./isInteger");
	isNaN = require("./isNaN");
	isSafeInteger = require("./isSafeInteger");
	MAX_SAFE_INTEGER = require("./MAX_SAFE_INTEGER");
	MIN_SAFE_INTEGER = require("./MIN_SAFE_INTEGER");
	NaN = 0 / 0;
	toExponential = require("./toExponential");
}


--!native
--!optimize 2
--!strict
return function(value)
	return type(value) == "number" and value == value and value ~= math.huge and value ~= -math.huge
end


--!native
--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
return function(value)
	return type(value) == "number" and value ~= math.huge and value == math.floor(value)
end


--!native
--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
return function(value)
	return type(value) == "number" and value ~= value
end


--!native
--!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)
	return isInteger(value) and math.abs(value) <= MAX_SAFE_INTEGER
end


--!native
--!optimize 2
--!strict
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential
return function(value: string | number, 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`

	local retval =
		string.gsub(string.gsub(string.gsub(string.format(formatString, num), "%+0", "+"), "%-0", "-"), "0*e", "e")

	return retval
end


--!native
--!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 {}


--!native
--!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(str: 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 = #str
	if index < 1 or index > length then
		return NaN
	end

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

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

	local value = utf8.codepoint(str, offset, offset)

	if value == nil then
		return NaN
	end

	return value
end


--!native
--!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


--!native
--!optimize 2
--!strict
type Match = {
	index: number,
	match: string,
}

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

local function findOr(str: string, patternTable: {string}, initIndex: number?): Match?
	-- loop through all options in patern patternTable

	local init = utf8.offset(str, initIndex or 1)
	local matches = {}
	for _, value in patternTable do
		value = string.gsub(value, luaPatternCharacters, "%%%1")
		local iStart, iEnd = string.find(str, value, init)
		if iStart then
			local prefix = string.sub(str, 1, iStart - 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 iStartIndex = prefixEnd :: number + 1
			table.insert(matches, {
				index = iStartIndex;
				match = string.sub(str, iStart, iEnd);
			})
		end
	end

	-- if no matches, return nil
	if #matches == 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 _, value in matches do
		-- load first condition
		if firstMatch == nil then
			firstMatch = value
		end

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

	-- return first match
	return firstMatch
end

return findOr


--!native
--!optimize 2
--!strict

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

local function includes(str: string, substring: string, position: (string | number)?): boolean
	local strLen, invalidBytePosition = utf8.len(str)
	assert(strLen ~= nil, string.format("string `%*` has an invalid byte at position %*", str, `{invalidBytePosition}`))
	if strLen == 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 > strLen then
			return false
		end
	end

	if startIndex < 1 then
		startIndex = 1
	end

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

return includes


--!native
--!optimize 2
--!strict

-- excluding the `+` and `*` character, since findOr tests and graphql use them explicitly
local luaPatternCharacters = "([" .. 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(str: string, searchElement: string, fromIndex: number?): number
	local length = #str
	local fromIndex_ = 1
	if fromIndex then
		fromIndex_ = if fromIndex < 1 then 1 else fromIndex :: number
	end

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

	if fromIndex_ > length then
		return -1
	end

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

	for i = fromIndex_, length do
		if string.sub(str, i, i + searchElementLength - 1) == searchElement then
			return i
		end
	end

	return -1
end


--!native
--!optimize 2
--!strict
return {
	charCodeAt = require("./charCodeAt");
	endsWith = require("./endsWith");
	findOr = require("./findOr");
	includes = require("./includes");
	indexOf = require("./indexOf");
	lastIndexOf = require("./lastIndexOf");
	slice = require("./slice");
	split = require("./split");
	startsWith = require("./startsWith");
	substr = require("./substr");
	trim = require("./trim");
	trimEnd = require("./trimEnd");
	trimStart = require("./trimStart");
	-- aliases for trimEnd and trimStart
	trimRight = require("./trimEnd");
	trimLeft = require("./trimStart");
}


--!native
--!optimize 2
--!strict
local function lastIndexOf(str: string, searchValue: string, fromIndex: number?): number
	local strLength = #str
	local calculatedFromIndex
	if fromIndex then
		calculatedFromIndex = fromIndex
	else
		calculatedFromIndex = strLength
	end

	if fromIndex and fromIndex < 1 then
		calculatedFromIndex = 1
	end

	if fromIndex and fromIndex > strLength then
		calculatedFromIndex = strLength
	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(str, 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


--!native
--!optimize 2
--!strict
local function slice(str: string, startIndexStr: string | number, lastIndexStr: (string | number)?): string
	local strLen, invalidBytePosition = utf8.len(str)
	assert(strLen ~= nil, string.format("string `%*` has an invalid byte at position %*", str, `{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(str, startIndex) :: number
	-- get char length of charset retunred at offset
	local lastIndexByte = (utf8.offset(str, lastIndex) :: number) - 1

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

return slice


--!native
--!optimize 2
--!strict
local Number = require("../Number")
local findOr = require("./findOr")
local slice = require("./slice")
local types = require("../ES7Types")

local MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER
type Array<T> = types.Array<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


--!native
--!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


--!native
--!optimize 2
--!strict
return function(s: string, startIndex: number, numberOfCharacters: number?): string
	if numberOfCharacters and numberOfCharacters <= 0 then
		return ""
	end

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


--!native
--!optimize 2
--!strict

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

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


--!native
--!optimize 2
--!strict
return function(source: string): string
	return (string.gsub(source, "[%s]+$", ""))
end


--!native
--!optimize 2
--!strict
return function(source: string): string
	return (string.gsub(source, "^[%s]+", ""))
end


--!native
--!optimize 2
local Symbol = require("./Symbol")

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

return {
	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;
}


--!native
--!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 {
	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;
}


--!native
--!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("./RegistryGlobal")
local Symbol = require("./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

return SymbolObject


--!native
--!optimize 2
--!strict

local task = require("@lune/task")
local Object = require("../Collections").Object

local makeIntervalImpl = require("./makeIntervalImpl")
local makeTimerImpl = require("./makeTimerImpl")

export type Timeout = makeTimerImpl.Timeout
export type Interval = makeIntervalImpl.Interval

return Object.assign({}, makeTimerImpl(task.delay), makeIntervalImpl(task.delay))


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

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

local SCHEDULED = 1
local CANCELLED = 3

return function(delayImpl)
	local function setInterval(callback, intervalTime: number, ...): Interval
		local args = {...}
		local task = {
			[Status] = SCHEDULED;
		}

		-- delayTime is an optional parameter
		if intervalTime == nil then
			intervalTime = 0
		end

		-- To mimic the JS interface, we're expecting delayTime to be in ms
		local intervalTimeMs = intervalTime / 1000
		local delay_
		delay_ = function()
			delayImpl(intervalTimeMs, function()
				if task[Status] == SCHEDULED then
					callback(unpack(args))
					delay_()
				end
			end)
		end

		delay_()

		return task
	end

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

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

	return {
		setInterval = setInterval;
		clearInterval = clearInterval;
	}
end


--!native
--!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(delayImpl)
	local function setTimeout(callback, delayTime: number?, ...): Timeout
		local args = {...}
		local task = {
			[Status] = SCHEDULED;
		}

		-- delayTime is an optional parameter
		if delayTime == nil then
			delayTime = 0
		end

		-- To mimic the JS interface, we're expecting delayTime to be in ms
		local delayTimeMs = delayTime :: number / 1000
		delayImpl(delayTimeMs, function()
			if task[Status] == SCHEDULED then
				callback(unpack(args))
				task[Status] = DONE
			end
		end)

		return task
	end

	local function clearTimeout(task: Timeout)
		if task == nil then
			return
		end
		if task[Status] == SCHEDULED then
			task[Status] = CANCELLED
		end
	end

	return {
		setTimeout = setTimeout;
		clearTimeout = clearTimeout;
	}
end