--!optimize 2
--!strict

local Fmt = require("@packages/Fmt")
local Spek = require("@packages/Spek")

return function(t: Spek.T)
	local describe = t.describe
	local expect = t.expect
	local expectError = t.expectError
	local it = t.it
	local measure = t.measure
	local operation = t.operation

	describe("Fmt")(function()
		it("should throw when format specifier is not closed")(function()
			expectError(function()
				Fmt("{")
			end)
		end)

		it("should throw when close brace has no matching open brace")(function()
			expectError(function()
				Fmt("}")
			end)
		end)

		it("should throw when there is one parameter and no argument")(function()
			expectError(function()
				Fmt("{}")
			end)
		end)

		it("should throw when there are more parameters than arguments")(function()
			expectError(function()
				Fmt("{}, {}", 1)
			end)
		end)

		it("should throw when there are more arguments than parameter")(function()
			expectError(function()
				Fmt("{}", 1, 2)
			end)
		end)

		it("should return an identical string")(function()
			expect(function()
				return Fmt("Hello, world!") == "Hello, world!"
			end)
		end)

		it("should escape")(function()
			expect(function()
				return Fmt("{{}}") == "{}"
			end)
		end)

		it("should replace parameter")(function()
			expect(function()
				return Fmt("Hello, {}!", "Gamer") == "Hello, Gamer!"
			end)
		end)

		it("should format positional parameter")(function()
			expect(function()
				return Fmt("{1} {} {0} {}", 1, 2) == "2 1 1 2"
			end)
		end)

		it("should format extra positional parameter")(function()
			expect(function()
				return Fmt("{} {1}", 1, 2) == "1 2"
			end)
		end)

		it("should throw with extra parameter")(function()
			expectError(function()
				Fmt("{} {1} {} {}", 1, 2)
			end)
		end)

		it("should return open brace literal")(function()
			expect(function()
				return Fmt("{{") == "{"
			end)
		end)

		it("should return open brace literal")(function()
			expect(function()
				return Fmt("}}") == "}"
			end)
		end)

		it("should throw with a negative positional parameter")(function()
			expectError(function()
				Fmt("{-1}", 1)
			end)

			expectError(function()
				Fmt("{1.5}", 1)
			end)
		end)

		it("should throw with an invalid positional parameter")(function()
			expectError(function()
				Fmt("{1}", 1)
			end)
		end)

		it("should throw when more arguments than positional parameters are provided")(function()
			expectError(function()
				Fmt("{2}", 1, 2)
			end)
		end)

		it("should format named parameter")(function()
			expect(function()
				return Fmt("Hello, {name}!", {
					name = "Gamer";
				}) == "Hello, Gamer!"
			end)
		end)

		it("should format named and regular parameters")(function()
			expect(function()
				return Fmt("Hello, {name}! How are {} today, {name}?", "you", {
					name = "Gamer";
				}) == "Hello, Gamer! How are you today, Gamer?"
			end)
		end)

		it("should format multiple named parameters")(function()
			expect(function()
				return Fmt("Hello, {name}! How {word} you doing {other}?", {
					word = "are";
					name = "Gamer";
					other = "today";
				}) == "Hello, Gamer! How are you doing today?"
			end)
		end)

		it("should format named, regular, and positional parameters")(function()
			expect(function()
				return Fmt("Hello, {name}! How {} you {1}, {name}?", "are", "today", {
					name = "Gamer";
				}) == "Hello, Gamer! How are you today, Gamer?"
			end)
		end)

		it("should throw with extra parameters")(function()
			expectError(function()
				Fmt("Hello, {name}! How {} you {1}, {name}? {} {}", "are", "today", {
					name = "Gamer";
				})
			end)
		end)

		it("should throw with extra arguments")(function()
			expectError(function()
				Fmt("Hello, {name}! How {} you {1}, {name}? {}", "are", "today", "yes", {
					name = "Gamer";
				})
			end)
		end)

		it("should use debug form")(function()
			expect(function()
				return Fmt("{}", "\"") == "\""
			end)
			expect(function()
				return Fmt("{:?}", "\"") == "\"\\\"\""
			end)
		end)

		it("should throw with invalid enhanced parameter")(function()
			expectError(function()
				Fmt("{:!}")
			end)
		end)

		it("should add spaces to fit minimum width")(function()
			expect(function()
				return Fmt("{:5}", "x") == "x    "
			end)
		end)

		it("should not add spaces when greater than minimum width")(function()
			expect(function()
				return Fmt("{:5}", "hello") == "hello"
			end)
		end)

		it("should handle sign")(function()
			expect(function()
				return Fmt("{positive} {positive:+} {negative:+} {zero:+} {message:+}", {
					positive = 5;
					negative = -5;
					zero = 0;
					message = "hello";
				}) == "5 +5 -5 +0 hello"
			end)
		end)

		it("should handle precision")(function()
			expect(function()
				return Fmt("{0:.5} {1:.3}", 5, 5.0001) == "5.00000 5.000"
			end)
		end)

		it("should handle leading zeros")(function()
			expect(function()
				return Fmt("{0:05} {1:02} {1:01}", 5, 42) == "00005 42 42"
			end)
		end)

		it("should handle array")(function()
			local array = {1, 2, 3}

			expect(function()
				return string.find(Fmt("{}", array), "table: 0x") ~= nil
			end)
			expect(function()
				return Fmt("{:?}", array) == "{[1] = 1, [2] = 2, [3] = 3}"
			end)
			expect(function()
				return Fmt("{:#?}", array) == "{\n    [1] = 1,\n    [2] = 2,\n    [3] = 3\n}"
			end)
		end)

		it("should handle edge case")(function()
			local buffer = table.pack(Fmt.Output("{} {} {}", "hello", 5, "yes"))
			expect(function()
				return buffer[1] == "hello 5 yes"
			end)
			expect(function()
				return buffer[2] == nil
			end)
		end)
	end)

	measure("none")(function()
		operation(function()
			for _ = 1, 100 do
				Fmt("Hello, world!")
			end
		end)
	end, "Fmt")

	measure("string")(function()
		local array = {
			"swagalicious";
			utf8.char(9757, 129299);
			"Hello!";
			`{utf8.char(129327)} Wow, what an exciting day! {utf8.char(10024)} Let's keep going!`;
			utf8.char(128077, 128076, 129310, 129311);
			"A long string without emojis just to see the performance!";
			`{utf8.char(9992, 65039)} Traveling is so much fun! {utf8.char(127757, 9996, 65039)}`;
		}

		operation(function()
			for _, value in array do
				Fmt("{}", value)
			end
		end)
	end, "Fmt")

	measure("multi-input")(function()
		-- Testing with a larger input to evaluate performance
		local array = table.create(100)
		for index = 1, 100 do
			array[index] = {math.random(-1E4, 1E4), "gaming", {math.random(-10, 10)}} :: {unknown}
		end

		operation(function()
			for _, values in array do
				Fmt("{} {}, {}", table.unpack(values, 1, 3))
			end
		end)
	end, "Fmt")

	measure("sign")(function()
		operation(function()
			for _ = 1, 100 do
				Fmt("{positive} {positive:+} {negative:+} {zero:+} {message:+}", {
					positive = 5;
					negative = -5;
					zero = 0;
					message = "hello";
				})
			end
		end)
	end, "Fmt")

	measure("precision")(function()
		operation(function()
			for _ = 1, 100 do
				Fmt("{0:.5} {1:.3}", 5, 5.0001)
			end
		end)
	end, "Fmt")

	measure("padding")(function()
		operation(function()
			for _ = 1, 100 do
				Fmt("{:5}", "x")
				Fmt("{:5}", "hello")
			end
		end)
	end, "Fmt")

	local ARRAY = {1, 2, 3}

	measure("{:?} table")(function()
		operation(function()
			for _ = 1, 100 do
				Fmt("{:?}", ARRAY)
			end
		end)
	end, "Fmt")

	measure("{:#?} table")(function()
		operation(function()
			for _ = 1, 100 do
				Fmt("{:#?}", ARRAY)
			end
		end)
	end, "Fmt")
end


--!optimize 2
--!strict

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

type Static = typeof(setmetatable(
	{} :: {
		CreateFormatter: typeof(CreateFormatter),
		Debugify: <T>(object: T, fmtFunction: (object: T, ...unknown) -> unknown) -> {[any]: any},
		Fmt: (templateString: string, ...unknown) -> string,
		Format: (templateString: string, ...unknown) -> string,
		Output: (templateString: string, ...unknown) -> ...unknown,
	},
	{} :: {__call: (self: any, templateString: string, ...unknown) -> string}
))

--[=[
	This is a reimplementation of Rust's fmt system.

	```lua
	print(Fmt("{}", "Hi"))
	print(Fmt("{}", {A = 1, B = 1}))
	print(Fmt("{:?}", {A = 1, B = 1}))
	print(Fmt("{:#?}", {A = 1, B = 1}))
	```

	@class Fmt
]=]
local Fmt = {}
Fmt.CreateFormatter = CreateFormatter

--[=[
	Wrap the given object in a type that implements the given function as its
	debug implementation, and forwards __tostring to the type's underlying
	tostring implementation.

	@param object T -- The object to debugify.
	@param fmtFunction (object: T, ...: unknown) -> unknown -- The fmt function.
	@return DebugObject
]=]
function Fmt.Debugify<T>(object: T, fmtFunction: (object: T, ...unknown) -> unknown)
	return setmetatable({}, {
		__FmtDebug = function(_, ...)
			return fmtFunction(object, ...)
		end;

		__tostring = function()
			return tostring(object)
		end;
	})
end

--[=[
	Formats the given TemplateString. This also works with `__call`.

	```lua
	print(Fmt("{}", "Hi")) -- Hi
	print(Fmt("{}", {A = 1, B = 1})) -- table: 0x3911424e137a940d
	print(Fmt("{:?}", {A = 1, B = 1})) -- {["A"] = 1, ["B"] = 2}
	print(Fmt("{:#?}", {A = 1, B = 1}))
	--[[
	{
		["A"] = 1,
		["B"] = 2
	}
	--]]

	print(Fmt("Hello, {Value}!", {
		Value = "world";
	})) -- Hello, world!

	print(Fmt("{1} {} {0} {}", 1, 2)) -- 2 1 1 2
	```

	@param templateString string -- The template string to format with.
	@param ...unknown -- The arguments to format with.
	@return string -- The formatted string.
]=]
function Fmt.Fmt(templateString: string, ...: unknown)
	local formatter = CreateFormatter()
	formatter("Write", templateString, ...)
	return formatter("AsString")
end

Fmt.Format = Fmt.Fmt

--[=[
	Returns a tuple of the formatted output.

	@param TemplateString string -- The template string to format with.
	@param ...unknown -- The arguments to format with.
	@return ...unknown -- The tuple.
]=]
function Fmt.Output(templateString: string, ...: unknown): ...unknown
	local formatter = CreateFormatter()
	formatter("Write", templateString, ...)
	return formatter("AsTuple")
end

local Metatable = {}
function Metatable:__call(templateString: string, ...: unknown)
	local formatter = CreateFormatter()
	formatter("Write", templateString, ...)
	return formatter("AsString")
end

return (table.freeze(setmetatable(Fmt, Metatable)) :: never) :: Static


--!optimize 2
--!strict

local GreenTea = require("@self/GreenTea")
local tCompat = require("@self/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.Instance = GreenTea.Instance

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

export.buffer = GreenTea.buffer

export.userdata = GreenTea.userdata

export.Vector2 = GreenTea.Vector2
export.vector = GreenTea.vector
export.Vector3 = GreenTea.Vector3
export.CFrame = GreenTea.CFrame
export.Color3 = GreenTea.Color3
export.UDim = GreenTea.UDim
export.UDim2 = GreenTea.UDim2
export.Ray = GreenTea.Ray
export.Rect = GreenTea.Rect
export.Region3 = GreenTea.Region3
export.BrickColor = GreenTea.BrickColor
export.Font = GreenTea.Font

export.Enum = GreenTea.Enum
export.EnumItem = GreenTea.EnumItem

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.anyFunction = GreenTea.anyfn
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

export.wrapFn = GreenTea.wrapFn
export.wrapFnArgs = GreenTea.wrapFnArgs
export.wrapFnReturns = GreenTea.wrapFnReturns

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
	end

	if type(thing) == "function" then
		return GreenTea.custom(thing)
	end

	if thing == nil then
		return GreenTea.none()
	end

	return GreenTea.typeof(thing)
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;
}

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()
	end

	if count == 1 then
		return GreenTea.literal(...)
	end

	local literals = {}
	for index = 1, count do
		table.insert(literals, GreenTea.literal(select(index, ...)))
	end

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

t.exactly = t.literal

t.keyOf = function(keyType: {[any]: any}): any
	local union = {}
	for key in next, 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 _, value in next, 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 next, 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 next, 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 castTypechecker = asGreenTeaType(typechecker)
	return function(...)
		castTypechecker: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 DateTime = require("@lune/DateTime")
local Debug = require("@packages/Debug")
local Error = require("@packages/Error")
local GreenTea = require("@packages/GreenTea")
local LuauPolyfill = require("@packages/LuauPolyfill")
local TypeError = require("@errors/TypeError")

local instanceof = LuauPolyfill.instanceof

local GreenTeaUtilities = {}

local IsMaybeString = GreenTea.build(GreenTea.optional(GreenTea.string()))
local IsTable = GreenTea.build(GreenTea.anyTable())

-- function GreenTeaUtilities.IsEnumeration<T>(enumType: T, name: string, enumeration: any): T
-- 	return GreenTea.withCustom(enumType, function(value: unknown): (boolean, string?)
-- 		local result = enumeration:CastResult(value)
-- 		if result.Success then
-- 			return true
-- 		end

-- 		return false, result.Error
-- 	end, `Enumeration.{name}`)
-- end

local USES_AN = {
	[97] = true;
	[101] = true;
	[105] = true;
	[111] = true;
	[117] = true;
}

local function GetIndefiniteArticle(value: string): string
	return if USES_AN[string.byte(value)] then "an" else "a"
end

function GreenTeaUtilities.IsLuauClass<T, A...>(classRoot: {new: (A...) -> T}, className: string, ...: A...): T
	local classObject = classRoot.new(...)
	local checker = GreenTea.withCustom(classObject, function(value): (boolean, any?)
		if not instanceof(value, classRoot) then
			return false, `expected {GetIndefiniteArticle(className)} {className}, got {Debug.Inspect(value)}`
		end

		return true
	end)

	if type(classObject) == "table" and type(classObject.Destroy) == "function" then
		pcall(function()
			classObject:Destroy()
		end)
	end

	return checker
end

local function InspectTable(classTable: any): string
	if type(classTable) == "table" then
		local metatable = getmetatable(classTable :: any)
		if metatable and type(metatable) == "table" and type(rawget(metatable, "__tostring")) == "function" then
			return tostring(classTable)
		end

		if type(classTable.ClassName) == "string" then
			return classTable.ClassName
		end
	end

	return Debug.Inspect(classTable)
end

type ConstructorClass<T> = {
	new: (...any) -> T,
	[any]: any,
}

function GreenTeaUtilities.IsInstanceOfLuauClassFromTable<T>(luauClass: ConstructorClass<T>, className: string?)
	do
		local typeSuccess0, typeError0 = IsTable:matches(luauClass)
		if not typeSuccess0 then
			local exception = TypeError.new(tostring(typeError0))
			Error.captureStackTrace(exception, GreenTeaUtilities.IsInstanceOfLuauClassFromTable)
			error(exception)
		end
		local typeSuccess1, typeError1 = IsMaybeString:matches(className)
		if not typeSuccess1 then
			local exception = TypeError.new(tostring(typeError1))
			Error.captureStackTrace(exception, GreenTeaUtilities.IsInstanceOfLuauClassFromTable)
			error(exception)
		end
	end
	assert(type(luauClass) == "table", "Luau")

	local trueClassName = className or "class object"
	local indefiniteArticle = if className then GetIndefiniteArticle(className) else "a"

	return GreenTea.withCustom((nil :: never) :: T, function(classObject): (boolean, any?)
		local typeSuccess, typeError = IsTable:matches(classObject)
		if not typeSuccess then
			return false, tostring(typeError)
		end

		local metatable = getmetatable(classObject)
		if not metatable then
			return false,
				`expected {indefiniteArticle} {trueClassName} with a metatable, got {InspectTable(classObject)}`
		end

		if type(metatable) ~= "table" then
			return false,
				`expected {indefiniteArticle} {trueClassName} with a table metatable, got __metatable = {Debug.Inspect(
					metatable
				)}`
		end

		if metatable.__index ~= luauClass then
			return false, `expected {indefiniteArticle} {trueClassName}, got {InspectTable(metatable)}`
		end

		return true
	end)
end

function GreenTeaUtilities.SmartLiteral<T>(...: T): T
	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))
end
GreenTeaUtilities.EasyLiteral = GreenTeaUtilities.SmartLiteral

function GreenTeaUtilities.SmartLiteralArray<T>(array: {T}): T
	local new = table.create(#array)

	for index, value in array do
		new[index] = GreenTea.literal(value)
	end

	return GreenTea.union(table.unpack(new))
end
function GreenTeaUtilities.SmartLiteralValues<T>(object: {[unknown]: T}): T
	local new = table.create(#object)
	local length = 0

	for _, value in object do
		length += 1
		new[length] = GreenTea.literal(value)
	end

	return GreenTea.union(table.unpack(new))
end

local IsString = GreenTea.build(GreenTea.string())

function GreenTeaUtilities.CaseInsensitiveLiterals<T>(...: string): T
	local count = select("#", ...)
	local array = table.create(count)
	local strings = table.create(count)

	for index = 1, count do
		local value = select(index, ...)
		local typeSuccess, typeError = IsString:matches(value)
		if not typeSuccess then
			local exception = Error.new(tostring(typeError))
			exception.name = "TypeError"
			Error.captureStackTrace(exception, GreenTeaUtilities.CaseInsensitiveLiterals)
			error(tostring(exception))
		end

		array[index] = GreenTea.literal(string.lower(value))
		strings[index] = string.format("%q", value)
	end

	local isValidLiterals = GreenTea.build(GreenTea.union(table.unpack(array)))
	return (
		GreenTea.withCustom("CaseInsensitive", function(value)
			local typeSuccess, typeError = IsString(value)
			if not typeSuccess then
				return false, typeError
			end

			return isValidLiterals(string.lower(value :: string))
		end, `CaseInsensitive<{table.concat(strings, " | ")}>`) :: unknown
	) :: T
end

type IntersectionType =
	& (<T1, T2>(T1, T2) -> T1 & T2)
	& (<T1, T2, T3>(T1, T2, T3) -> T1 & T2 & T3)
	& (<T1, T2, T3, T4>(T1, T2, T3, T4) -> T1 & T2 & T3 & T4)
	& (<T1, T2, T3, T4, T5>(T1, T2, T3, T4, T5) -> T1 & T2 & T3 & T4 & T5)
	& (<T1, T2, T3, T4, T5, T6>(T1, T2, T3, T4, T5, T6) -> T1 & T2 & T3 & T4 & T5 & T6)
	& (<T1, T2, T3, T4, T5, T6, T7>(T1, T2, T3, T4, T5, T6, T7) -> T1 & T2 & T3 & T4 & T5 & T6 & T7)
	& (<T1, T2, T3, T4, T5, T6, T7, T8>(T1, T2, T3, T4, T5, T6, T7, T8) -> T1 & T2 & T3 & T4 & T5 & T6 & T7 & T8)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9
	) -> T1 & T2 & T3 & T4 & T5 & T6 & T7 & T8 & T9)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9,
		T10
	) -> T1 & T2 & T3 & T4 & T5 & T6 & T7 & T8 & T9 & T10)
	& (<T>(...T) -> T)
type UnionType =
	& (<T1, T2>(T1, T2) -> T1 | T2)
	& (<T1, T2, T3>(T1, T2, T3) -> T1 | T2 | T3)
	& (<T1, T2, T3, T4>(T1, T2, T3, T4) -> T1 | T2 | T3 | T4)
	& (<T1, T2, T3, T4, T5>(T1, T2, T3, T4, T5) -> T1 | T2 | T3 | T4 | T5)
	& (<T1, T2, T3, T4, T5, T6>(T1, T2, T3, T4, T5, T6) -> T1 | T2 | T3 | T4 | T5 | T6)
	& (<T1, T2, T3, T4, T5, T6, T7>(T1, T2, T3, T4, T5, T6, T7) -> T1 | T2 | T3 | T4 | T5 | T6 | T7)
	& (<T1, T2, T3, T4, T5, T6, T7, T8>(T1, T2, T3, T4, T5, T6, T7, T8) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9
	) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9,
		T10
	) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9,
		T10,
		T11
	) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10 | T11)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9,
		T10,
		T11,
		T12
	) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10 | T11 | T12)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9,
		T10,
		T11,
		T12,
		T13
	) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10 | T11 | T12 | T13)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9,
		T10,
		T11,
		T12,
		T13,
		T14
	) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10 | T11 | T12 | T13 | T14)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9,
		T10,
		T11,
		T12,
		T13,
		T14,
		T15
	) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10 | T11 | T12 | T13 | T14 | T15)
	& (<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(
		T1,
		T2,
		T3,
		T4,
		T5,
		T6,
		T7,
		T8,
		T9,
		T10,
		T11,
		T12,
		T13,
		T14,
		T15,
		T16
	) -> T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10 | T11 | T12 | T13 | T14 | T15 | T16)
	& (<T>(...T) -> T)

GreenTeaUtilities.Intersection = GreenTea.intersection :: IntersectionType
GreenTeaUtilities.Union = GreenTea.union :: UnionType

-- Missing for some reason...
GreenTeaUtilities.DateTime = GreenTea.withCustom(DateTime.now(), function(value): (boolean, any?)
	if typeof(value) ~= "DateTime" then
		return false, `expected DateTime, got {typeof(value)}`
	end

	return true
end, "DateTime")

GreenTeaUtilities.SafeString = GreenTea.withCustom("", function(value): (boolean, any?)
	if type(value) ~= "string" then
		return false, `expected string, got {typeof(value)}`
	end

	if not utf8.len(value) then
		return false, `expected valid string, got {value}`
	end

	return true
end, "SafeString")

local function AsType(value: any)
	if GreenTea.isGreenTeaType(value) then
		return value
	end

	if type(value) == "function" then
		return GreenTea.custom(value)
	end

	if value == nil then
		return GreenTea.none()
	end

	return GreenTea.typeof(value)
end

function GreenTeaUtilities.InstanceWithChildren<T, C>(root: T, children: C): C & T
	assert(type(children) == "table", "children must be a table.")
	local childrenMapped = {}
	for key, childType in children do
		assert(type(key) == "string", "children keys must be strings.")
		childrenMapped[key] = AsType(childType)
	end

	local rootType = AsType(root)

	return GreenTea.withCustom(rootType :: T & C, function(value): (boolean, any?)
		if not value or typeof(value) ~= "Instance" then
			return false, `expected an instance, got {Debug.Inspect(value)}`
		end

		for key, typeCheck in childrenMapped do
			local child = value:FindFirstChild(key)
			if not child then
				return false, `missing child {key}`
			end

			local success, exception = typeCheck(child)
			if not success then
				return false, exception
			end
		end

		local existingChildren = {}
		for _, child in value:GetChildren() do
			if existingChildren[child.Name] then
				return false, `duplicate child {child.Name}`
			end

			existingChildren[child.Name] = true
		end

		return true
	end)
end

function GreenTeaUtilities.InstanceWithChildrenAllowDuplicates<T, C>(root: T, children: C): C & T
	assert(type(children) == "table", "children must be a table.")
	local childrenMapped = {}
	for key, childType in children do
		assert(type(key) == "string", "children keys must be strings.")
		childrenMapped[key] = AsType(childType)
	end

	local rootType = AsType(root)

	local instanceType = GreenTea.withCustom(rootType :: T & C, function(value): (boolean, any?)
		if not value or typeof(value) ~= "Instance" then
			return false, `expected an instance, got {Debug.Inspect(value)}`
		end

		for key, typeCheck in childrenMapped do
			local child = value:FindFirstChild(key)
			if not child then
				return false, `missing child {key}`
			end

			local success, exception = typeCheck(child)
			if not success then
				return false, exception
			end
		end

		return true
	end)

	return instanceType
end

function GreenTeaUtilities.MakeStrict<T>(object: T): T & {[any]: never}
	do
		local typeSuccess, typeError = IsTable:matches(object)
		if not typeSuccess then
			local exception = TypeError.new(tostring(typeError))
			Error.captureStackTrace(exception, GreenTeaUtilities.MakeStrict)
			error(exception)
		end
	end
	assert(type(object) == "table", "give me extends")

	local strictObject = table.clone(object) :: T & {[any]: never}
	assert(type(strictObject) == "table", "cringe")
	strictObject[GreenTea.any()] = GreenTea.never()
	return strictObject
end

function GreenTeaUtilities.Set<T>(key: T): {[T]: true}
	return {[AsType(key)] = GreenTea.literal(true) :: true}
end
function GreenTeaUtilities.SafeSet<T>(key: T): {[T]: boolean}
	return {[AsType(key)] = GreenTea.boolean()}
end

return table.freeze(GreenTeaUtilities)


--!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(object: any, class: any): boolean
	if __DEV__ then
		assert(type(class) == "table", "Received a non-table as the second argument for instanceof")
	end

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

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

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

	if success and hasNew then
		return true
	end

	local seen = {[object] = true}

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

			if object == class then
				return true
			end
		end

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

			seen[object] = true
		end
	end

	return false
end

return InstanceOf


--!optimize 2
--!strict

-- stylua: ignore
local HEX_TO_BINARY = {
    ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011",
    ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111",
    ["8"] = "1000", ["9"] = "1001", ["a"] = "1010", ["b"] = "1011",
    ["c"] = "1100", ["d"] = "1101", ["e"] = "1110", ["f"] = "1111",
}

--- A constant representing the 64-bit representation of `0`.
local ZERO = vector.create(0, 0, 0)
--- A constant representating the 64-bit representation of `1`.
local ONE = vector.create(0, 0, 1)
--- A constant representating the 64-bit representation of `2`.
local TWO = vector.create(0, 0, 2)

--- A constant representing the maximum possible 64-bit value
--- (`18446744073709551615`, or `2 ^ 64 - 1`)
local MAX = vector.create(0x3fffff, 0xfffff, 0x3fffff)
--- A constant representing the maximum possible 32-bit value
--- (`4294967295`, or `2 ^ 32 - 1`).
local MAX_U32 = vector.create(0, 0x3ff, 0x3fffff)
--- A constant representing the maximum integer that is exactly representable
--- by a normal Luau number (`9007199254740992` or `2 ^ 53`).
local MAX_F64 = vector.create(0x800, 0, 0)
--- A constant representing the maximum integer that is exactly representable
--- by a 32-bit floating point value (`16777216`, or `2 ^ 24`).
local MAX_F32 = vector.create(0, 4, 0)

--[=[
	Normalizes a vector, trimming off any bits that would interfere with
	the library's functionality.
]=]
local function normalize(u64: vector): vector
	return vector.create(
		bit32.band(u64.x, 0b1111_1111_1111_1111_1111_11),
		bit32.band(u64.y, 0b1111_1111_1111_1111_1111),
		bit32.band(u64.z, 0b1111_1111_1111_1111_1111_11)
	)
end

--[=[
	Constructs a 64-bit integer from the passed `u32` values.

	The provided `most` value fills the upper 32 bits of the integer,
	and the provided `least` value fills the lower 32-bits.
]=]
local function from_pair(most: number, least: number): vector
	-- 32 - 22 = 10
	-- x = upper 22 of most
	-- y = lower 10 of most and upper 10 of least
	-- z = lower 22 of least
	return vector.create(
		bit32.rshift(most, 10),
		bit32.bor(bit32.extract(least, 22, 10), bit32.lshift(bit32.band(most, 0b1111_1111_11), 10)),
		bit32.band(least, 0b1111_1111_1111_1111_1111_11)
	)
end

--[=[
	Constructs a 64-bit integer from the passed `u32`.

	This number is truncated in a 32-bit integer, even if it is larger than
	the max value.
]=]
local function from_u32(u32: number): vector
	return vector.create(0, bit32.rshift(u32, 22), bit32.band(u32, 0b1111_1111_1111_1111_1111_11))
end

--[=[
	Constructs a 64-bit integer from the passed `f64`.
	Put more plainly, converts a normal Luau number to a 64-bit integer.

	The number is truncated into an integer first. Note that Luau numbers
	cannot accurately represent integers past `2 ^ 53`.
]=]
local function from_f64(f64: number): vector
	return from_pair(bit32.bor(f64 // 2 ^ 32), bit32.bor(f64 % 2 ^ 32))
end

--[=[
	Converts the provided `u64` into two 32-bit integers.

	This returns the most significant portion of the number first.
]=]
local function to_pair(u64: vector): (number, number)
	-- x = upper 22 of most
	-- y = lower 10 of most and upper 10 of least
	-- z = lower 22 of least
	local most = bit32.bor(bit32.lshift(u64.x, 10), bit32.rshift(u64.y, 10))
	local least = bit32.bor(bit32.lshift(u64.y, 22), u64.z)

	return most, least
end

--[=[
	Converts the provided `u64` into four 16-bit integers.

	This returns the most significant portion of the number first.
]=]
local function to_quartet(u64: vector): (number, number, number, number)
	-- 0000000000000000000000 00000000000000000000 0000000000000000000000
	-- 0000000000000000 000000 0000000000 0000000000 000000 0000000000000000
	-- 1111000111100010 1101001111000100 1000000010011010 1011110011011110
	local most, least = to_pair(u64)
	local q1, q2 = bit32.rshift(most, 16), bit32.band(most, 0xffff)
	local q3, q4 = bit32.rshift(least, 16), bit32.band(least, 0xffff)
	-- local q1 = bit32.rshift(u64.x, 6)
	-- local q2 = bit32.bor(bit32.lshift(u64.y, 16), bit32.rshift(u64.y, 10))
	-- local q3 = bit32.bor(bit32.band(u64.y, 0b1111_1111_11), bit32.rshift(u64.z, 16))
	-- local q4 = bit32.band(0b1111_1111_1111_1111)
	return q1, q2, q3, q4
end

--[=[
	Converts the provided `u64` to an `f64` (or put plainly: a normal Luau
	number).

	This function does not check for precision loss. `f64` values lose precision
	past `2 ^ 53`.
]=]
local function to_f64(u64: vector): number
	local most, least = to_pair(u64)
	return most * 2 ^ 32 + least
end

--[=[
	Returns whether the provided `u64` is zero or not.

	Equivalent to `u64 == from_u32(0)`.
]=]
local function is_zero(u64: vector): boolean
	return u64 == ZERO
end

--[=[
	Computes the bitwise AND of the two provide values.

	This does not accept a vararg like the `bit32` equivalent for performance
	reasons.
]=]
local function band(lhs: vector, rhs: vector): vector
	return vector.create(bit32.band(lhs.x, rhs.x), bit32.band(lhs.y, rhs.y), bit32.band(lhs.z, rhs.z))
end

--[=[
	Computes the bitwise OR of the two provide values.

	This does not accept a vararg like the `bit32` equivalent for performance
	reasons.
]=]
local function bor(lhs: vector, rhs: vector): vector
	return vector.create(bit32.bor(lhs.x, rhs.x), bit32.bor(lhs.y, rhs.y), bit32.bor(lhs.z, rhs.z))
end

--[=[
	Computes the bitwise XOR of the two provide values.

	This does not accept a vararg like the `bit32` equivalent for performance
	reasons.
]=]
local function bxor(lhs: vector, rhs: vector): vector
	return vector.create(bit32.bxor(lhs.x, rhs.x), bit32.bxor(lhs.y, rhs.y), bit32.bxor(lhs.z, rhs.z))
end

--[=[
	Computes the bitwise negation of the provided value.
]=]
local function bnot(u64: vector): vector
	return vector.create(
		bit32.band(bit32.bnot(u64.x), 0b1111_1111_1111_1111_1111_11),
		bit32.band(bit32.bnot(u64.y), 0b1111_1111_1111_1111_1111),
		bit32.band(bit32.bnot(u64.z), 0b1111_1111_1111_1111_1111_11)
	)
end

--[=[
	Shifts the provided `u64` logically left by `n` bits.

	This function will error if `n` is negative.
]=]
local function lshift(u64: vector, n: number): vector
	if n >= 64 then
		return vector.create(0, 0, 0)
	elseif n == 0 then
		return u64
	end
	local most, least = to_pair(u64)
	if n < 32 then
		local remainder = 32 - n
		local ret = from_pair(bit32.replace(bit32.rshift(least, remainder), most, n, remainder), bit32.lshift(least, n))
		return ret
	else
		local ret = from_pair(bit32.lshift(least, n - 32), 0)
		return ret
	end
end

--[=[
	Shifts the provided `u64` logically right by `n` bits.

	This function will error if `n` is negative.
]=]
local function rshift(u64: vector, n: number): vector
	if n == 0 then
		return u64
	end
	local most, least = to_pair(u64)
	if n < 32 then
		local ret = from_pair(bit32.rshift(most, n), bit32.replace(bit32.rshift(least, n), most, 32 - n, n))
		return ret
	else
		local ret = from_pair(0, bit32.rshift(most, n - 32))
		return ret
	end
end

--[=[
	Shifts the provided `u64` arithmetically right by `n` bits. Since these
	numbers are unsigned, this effectively just copies the most significant
	bit into the empty space rather than filling them with zeros.

	This function will error if `n` is negative.
]=]
local function arshift(u64: vector, n: number): vector
	-- If the sign bit is set, we do arshift
	if bit32.btest(u64.x, 0x20_0000) then
		if n >= 64 then
			return MAX
		else
			return bor(rshift(u64, n), bnot(rshift(MAX, n)))
		end
	else
		-- Otherwise it's just an rshift
		return rshift(u64, n)
	end
end

--[=[
	Rotates the bits of the provided `u64` left by `n` bits.

	If `n` is negative, this is equivalent to `rrotate`. Otherwise, if `n` is
	greater than 64-bits, it will wrap around.
]=]
local function lrotate(u64: vector, n: number): vector
	n = bit32.band(n, 63)
	local lshifted = lshift(u64, n)
	local rshifted = rshift(u64, 64 - n)
	return bor(lshifted, rshifted)
end

--[=[
	Rotates the bits of the provided `u64` right by `n` bits.

	If `n` is negative, this is equivalent to `lrotate`. Otherwise, if `n` is
	greater than 64-bits, it will wrap around.
]=]
local function rrotate(u64: vector, n: number): vector
	n = bit32.band(n, 63)
	local lshifted = lshift(u64, 64 - n)
	local rshifted = rshift(u64, n)
	return bor(lshifted, rshifted)
end

--[=[
	Returns the number of consecutive zero bits in the provided `u64` starting
	from the left-most (most significant) bit.
]=]
local function countlz(u64: vector): number
	local most, least = to_pair(u64)
	if most == 0 then
		return bit32.countlz(least) + 32
	else
		return bit32.countlz(most)
	end
end

--[=[
	Returns the number of consecutive zero bits in the provided `u64` starting
	from the right-most (least significant) bit.
]=]
local function countrz(u64: vector): number
	local most, least = to_pair(u64)
	if least == 0 then
		return bit32.countrz(most) + 32
	else
		return bit32.countrz(least)
	end
end

--[=[
	Returns a boolean describing whether the bitwise AND of `lhs` and
	`rhs` are different than zero.

	This does not accept a vararg like the `bit32` equivalent for performance
	reasons.
]=]
local function btest(lhs: vector, rhs: vector): boolean
	return band(lhs, rhs) ~= ZERO
end

--[=[
	Returns the provided `u64` with the order of bytes swapped.
]=]
local function byteswap(u64: vector): vector
	local most, least = to_pair(u64)
	return from_pair(bit32.byteswap(least), bit32.byteswap(most))
end

--[=[
	Returns whether `lhs` is greater than `rhs`.
]=]
local function gt(lhs: vector, rhs: vector): boolean
	local l_most, l_least = to_pair(lhs)
	local r_most, r_least = to_pair(rhs)

	if l_most == r_most then
		return l_least > r_least
	else
		return l_most > r_most
	end
end

--[=[
	Returns whether `lhs` is greater than or equal to `rhs`.
]=]
local function gt_equal(lhs: vector, rhs: vector): boolean
	return lhs == rhs or gt(lhs, rhs)
end

--[=[
	Returns whether `lhs` is less than `rhs`.
]=]
local function lt(lhs: vector, rhs: vector): boolean
	local l_most, l_least = to_pair(lhs)
	local r_most, r_least = to_pair(rhs)

	if l_most == r_most then
		return l_least < r_least
	else
		return l_most < r_most
	end
end

--[=[
	Returns whether `lhs` is less than or equal to `rhs`.
]=]
local function lt_equal(lhs: vector, rhs: vector): boolean
	return lhs == rhs or lt(lhs, rhs)
end

--[=[
	Calculates the quotient of the two provided values and returns it,
	along with the remainder from that operation.

	This function operates on a single 64-bit integer and a 32-bit integer.
	For dividing two 64-bit integers, see `div`.

	Equivalent to `//` for normal integers.
	This function will error if `divisor` is 0.
]=]
local function div_32(dividend: vector, divisor: number): (vector, number)
	if divisor == 0 then
		error("cannot divide integers by zero")
	end
	local hi, lo = to_pair(dividend)
	local q_hi, q_lo = 0, 0
	local rem = 0

	for i = 31, 0, -1 do
		rem = bit32.bor(bit32.lshift(rem, 1), bit32.band(bit32.rshift(hi, i), 1))

		if rem >= divisor then
			rem -= divisor
			q_hi = bit32.bor(q_hi, bit32.lshift(1, i))
		end
	end
	for i = 31, 0, -1 do
		rem = bit32.bor(bit32.lshift(rem, 1), bit32.band(bit32.rshift(lo, i), 1))

		if rem >= divisor then
			rem -= divisor
			q_lo = bit32.bor(q_lo, bit32.lshift(1, i))
		end
	end

	return from_pair(q_hi, q_lo), rem
end

--[=[
	Calculates the sum of the two provided values. Equivalent to `+` for
	normal integers.

	If the sum is equal to or greater than `2 ^ 64`, the returned value will
	overflow rather than expanding beyond 64 bits.
]=]
local function add(augend: vector, addend: vector): vector
	local prime = augend + addend

	prime += vector.create(if prime.y > (2 ^ 20 - 1) then 1 else 0, if prime.z > (2 ^ 22 - 1) then 1 else 0, 0)

	return normalize(prime)
end

--[=[
	Calculates the difference of the two provided values. Equivalent to `-` for
	normal integers.

	If the difference is less than `0`, the returned value will overflow rather
	than going negative.
]=]
local function sub(minuend: vector, subtrahend: vector): vector
	local prime = minuend - subtrahend

	local adjust_z = prime.z
	local adjust_y = prime.y
	local adjust_x = prime.x

	local borrow_z = prime.z < 0
	if borrow_z then
		adjust_z += 2 ^ 22
	end

	if borrow_z then
		adjust_y -= 1
	end
	local borrow_y = adjust_y < 0
	if borrow_y then
		adjust_y += 2 ^ 20
	end

	if borrow_y then
		adjust_x -= 1
	end
	local borrow_x = adjust_x < 0
	if borrow_x then
		adjust_x += 2 ^ 22
	end

	return vector.create(adjust_x, adjust_y, adjust_z)
end

--[=[
	Calculates the product of the two provided values. Equivalent to `*` for
	normal integers.

	If the product is greater than or equal to `2 ^ 64`, the returned value will
	overflow rather than expanding beyond 64 bits.
]=]
local function mult(multiplier: vector, multiplicand: vector): vector
	-- We represent 64-bit numbers as two 32-bit ones.
	-- Multiplying them is:
	-- (A + B) * (C + D) = (A * C) + (A * D) + (B * C) + (B * D)
	--
	-- However, multiplying two 32-bit numbers might overflow. So, we need to
	-- use 16-bit numbers. This turns our math into this:
	-- (A + B + C + D) * (E + F + G + H) =
	-- 		(A * E) + (A * F) + (A * G) + (A * H) +
	-- 		(B * E) + (B * F) + (B * G) + (B * H) +
	-- 		(C * E) + (C * F) + (C * G) + (C * H) +
	-- 		(D * E) + (D * F) + (D * G) + (D * H)
	--
	-- We can skip (A * E), (A * F), (A * G), (B * E), (B * F), and (C * F)
	-- because they don't exist within the bounds of the final product.
	-- Since the numbers are built as A * 2 ^ 48 + B * 2 ^ 32 + C * 2 ^ 16 + D
	-- you end up with e.g. (A * 2 ^ 48 * E * 2 ^ 48) which is A * E * 2^96.
	-- Otherwise... Here we go.

	local a, b, c, d = to_quartet(multiplier)
	local e, f, g, h = to_quartet(multiplicand)

	local product_4 = d * h
	local product_3 = bit32.rshift(product_4, 16) + c * h
	local product_2 = bit32.rshift(product_3, 16)
	product_3 = bit32.band(product_3, 0xFFFF) + d * g

	product_2 += bit32.rshift(product_3, 16) + b * h
	local product_1 = bit32.rshift(product_2, 16)

	product_2 = bit32.band(product_2, 0xFFFF) + c * g
	product_1 += bit32.rshift(product_2, 16)
	product_2 = bit32.band(product_2, 0xFFFF) + d * f
	product_1 += bit32.rshift(product_2, 16) + a * h + b * g + c * f + d * e

	-- We skip truncating any of the products the last time since they'll never
	-- overflow and bit32 will truncate them for us.

	return from_pair(bit32.replace(product_2, product_1, 16, 16), bit32.replace(product_4, product_3, 16, 16))
end

--[=[
	Calculates the quotient of the two provided values and returns it, along
	with the remainder.

	Equivalent to `//` and `%` for normal integers.
	This function will error if `divisor` is `0`.
]=]
local function div(dividend: vector, divisor: vector): (vector, vector)
	if is_zero(divisor) then
		error("cannot divide integers by zero")
	elseif countlz(dividend) >= 11 and countlz(divisor) >= 11 then
		local l_real = to_f64(dividend)
		local r_real = to_f64(divisor)
		return from_f64(l_real // r_real), from_f64(l_real % r_real)
	end

	local quotient = ZERO
	local remainder = dividend

	-- Doing extra iterations of this is very bad!
	for i = math.abs(countlz(dividend) - countlz(divisor)), 0, -1 do
		local shifted_divisor = lshift(divisor, i)

		if gt_equal(remainder, shifted_divisor) then
			remainder = sub(remainder, shifted_divisor)
			quotient = bor(quotient, lshift(ONE, i))
		end
	end

	return quotient, remainder
end

--[=[
	Calculates the result of `base` raised to `power`. Equivalent to `^` for
	normal integers. `power` is interpreted as a 32-bit integer.

	If the result is greater than or equal to `2 ^ 64`, the returned value will
	overflow rather than expanding beyond 64 bits.

	Additionally, `0 ^ 0` is treated as being `1`.
]=]
local function pow(base: vector, power: number): vector
	-- TODO: Is there an efficient solution that isn't recursion?
	if power == 0 then
		return ONE
	elseif power % 2 == 1 then
		return mult(base, pow(base, power - 1))
	else
		local temp = pow(base, power / 2)
		return mult(temp, temp)
	end
end

--[=[
	Reads a `u64` from the provided buffer. If offset is provided, reads from
	that spot in the buffer. Otherwise, reads from the beginning.

	This function reads a little-endian value from the buffer.
]=]
local function from_buffer(buf: buffer, offset: number?): vector
	offset = offset or 0
	local least = buffer.readu32(buf, offset :: number)
	local most = buffer.readu32(buf, offset :: number + 4)

	return from_pair(most, least)
end

--[=[
	Reads a `u64` from the provided string. If offset is provided, reads from
	that point in the string. Otherwise starts at the beginning.

	This function reads a little-endian value from the string.
]=]
local function from_byte_string(str: string, offset: number?): vector
	offset = offset or 1
	-- This is deliberately backwards because we expect a little endian input
	local d, c, b, a, h, g, f, e = string.byte(str, offset, offset :: number + 7)
	local least = bit32.bor(bit32.lshift(bit32.replace(b, a, 8, 8), 16), bit32.replace(d, c, 8, 8))
	local most = bit32.bor(bit32.lshift(bit32.replace(f, e, 8, 8), 16), bit32.replace(h, g, 8, 8))

	return from_pair(most, least)
end

--[=[
	Converts the provided `u64` to a buffer.

	The integer is written as a little-endian value.
]=]
local function to_buffer(u64: vector): buffer
	local most, least = to_pair(u64)
	local buf = buffer.create(8)
	-- We use a little endian format because it doesn't matter and most
	-- systems we run on are little endian so it'll be nicer for them.
	buffer.writeu32(buf, 0, least)
	buffer.writeu32(buf, 4, most)
	return buf
end

--[=[
	Writes the provided `u64` to the provided buffer. The value is written
	to the buffer at the provided offset.

	The integer is written as a little-endian value.
]=]
local function write_to_buffer(b: buffer, u64: vector, offset: number)
	local most, least = to_pair(u64)
	buffer.writeu32(b, offset, least)
	buffer.writeu32(b, offset + 4, most)
end

--[=[
	Converts the provided `u64` to string of binary data.

	The integer is written as if it were little-endian.
]=]
local function to_byte_string(u64: vector): string
	local most, least = to_pair(u64)
	-- Previous implementation of this used `%c` but it turns out that
	-- `string.format("%c", 0)` results in a zero length string!
	return string.char(
		bit32.band(least, 0xFF),
		bit32.extract(least, 8, 8),
		bit32.extract(least, 16, 8),
		bit32.rshift(least, 24),
		bit32.band(most, 0xFF),
		bit32.extract(most, 8, 8),
		bit32.extract(most, 16, 8),
		bit32.rshift(most, 24)
	)
end

--[=[
	Converts the provided `u64` to a string of 16 hexadecimal digits.

	The returned string will always be 16 bytes and the number is formatted as
	if it were big-endian. It also always uses lowercase letters.
]=]
local function to_hex_string(u64: vector): string
	local most, least = to_pair(u64)
	return string.format("%08x%08x", most, least)
end

--[=[
	Converts the provided `u64` to a string of 64 binary digits.

	The returned string will always be 64 bytes and the number is formatted as
	if it were big-endian.
]=]
local function to_bin_string(u64: vector): string
	-- Manually implemtning a binary conversion is not any faster, and
	-- this is a lot easier.
	return (string.gsub(to_hex_string(u64), "(.)", HEX_TO_BINARY))
end

--[=[
	Returns the provided value parsed as a 64-bit integer. Expects the provided
	string to contain only the digits 0-9 and will error if it does not.

	Leading zeros are accepted and parsed appropriately.

	Due to the implementation, the resulting integer will wrap around if it is
	too large to fit in a 64-bit integer rather than erroring.
]=]
local function from_dec_string(str: string): vector
	if string.find(str, "%D") then
		local pos, digit = string.match(str, "()(%D)")
		local hex = string.format("0x%02x", string.byte(digit :: any))
		error(`unexpected character '{digit}' ({hex}) at position {pos} while parsing decimal string`)
	end
	local low, high = 0, 0
	for i = 1, #str do
		-- The ascii code for zero is `48`.
		local digit = string.byte(str, i, i) - 48

		low *= 10
		high = high * 10 + low // 2 ^ 32
		low = bit32.bor(low, 0) + digit

		if low >= 2 ^ 32 then
			low -= 2 ^ 32
			high += 1
		end
	end

	return from_pair(high, low)
end

--[=[
	Returns the provided value as a string of decimal digits.

	Equivalent to formatting normal numbers with `%u` or
	calling `tostring` on them.
]=]
local function to_dec_string(u64: vector): string
	if countlz(u64) >= 11 then
		return tostring(to_f64(u64))
	else
		local q, rem = div_32(u64, 1e9)
		local q_hi, q_lo = to_pair(q)
		if q_hi == 0 and q_lo < 1e9 then
			return string.format("%u%09u", q_lo, rem)
		else
			return string.format("%s%09u", to_dec_string(q), rem)
		end
	end
end

--[=[
	Calculates the modulo of the two provided values. Equivalent to `%` for
	normal integers.

	This function will error if `divisor` is `0`.
]=]
local function mod(dividend: vector, divisor: vector): vector
	if divisor == ZERO then
		error("cannot divide integers by 0")
	end

	return sub(dividend, mult(div(dividend, divisor), divisor))
end

-- TODO: replace and extract
-- TODO: vararg versions of band, bor, bxor

return table.freeze({
	--- A constant representing the 64-bit representation of `0`.
	ZERO = ZERO;
	--- A constant representating the 64-bit representation of `1`.
	ONE = ONE;
	--- A constant representating the 64-bit representation of `2`.
	TWO = TWO;
	--- A constant representing the maximum possible 64-bit value
	--- (`18446744073709551615`, or `2 ^ 64 - 1`)
	MAX = MAX;
	--- A constant representing the maximum possible 32-bit value
	--- (`4294967295`, or `2 ^ 32 - 1`).
	MAX_U32 = MAX_U32;
	--- A constant representing the maximum integer that is exactly representable
	--- by a normal Luau number (`9007199254740992` or `2 ^ 53`).
	MAX_F64 = MAX_F64;
	--- A constant representing the maximum integer that is exactly representable
	--- by a 32-bit floating point value (`16777216`, or `2 ^ 24`).
	MAX_F32 = MAX_F32;

	from_pair = from_pair;
	from_u32 = from_u32;
	from_f64 = from_f64;

	to_pair = to_pair;
	to_quartet = to_quartet;
	to_f64 = to_f64;

	lt = lt;
	lt_equal = lt_equal;
	gt = gt;
	gt_equal = gt_equal;
	is_zero = is_zero;

	band = band;
	bor = bor;
	bxor = bxor;
	btest = btest;

	bnot = bnot;
	byteswap = byteswap;
	countlz = countlz;
	countrz = countrz;

	rshift = rshift;
	arshift = arshift;
	rrotate = rrotate;
	lshift = lshift;
	lrotate = lrotate;

	add = add;
	sub = sub;
	mult = mult;
	mod = mod;
	div = div;
	pow = pow;

	to_byte_string = to_byte_string;
	from_byte_string = from_byte_string;

	to_buffer = to_buffer;
	write_to_buffer = write_to_buffer;
	from_buffer = from_buffer;

	to_hex_string = to_hex_string;
	to_bin_string = to_bin_string;

	to_dec_string = to_dec_string;
	from_dec_string = from_dec_string;
})


local None = table.freeze(setmetatable({}, {
	__index = function()
		error("attempt to index on None symbol")
	end;
	__tostring = function()
		return "<None>"
	end;
}))

return None


local WEAK_KEYS_METATABLE = {__mode = "k"}

local function WeakMap<K, V>(default: {[K]: V}?): {[K]: V}
	local value = default or {}
	return setmetatable(value :: any, WEAK_KEYS_METATABLE) :: any
end

return WeakMap


local function all<T>(array: {T}, predicate: (element: T, index: number) -> boolean): boolean
	for index, element in array do
		if not predicate(element, index) then
			return false
		end
	end
	return true
end

return all


local reduce = require("./reduce")

local function alternate<T, U>(array: {T}, ...: {U}): {any}
	local alternateWith = {...}

	if #alternateWith == 0 then
		return array
	end

	local minimumLength = reduce(alternateWith, function(minimum: number, value)
		return math.min(minimum, #value)
	end, #array)

	if minimumLength == 0 then
		return {}
	end

	local result = {}

	for i = 1, minimumLength do
		table.insert(result, array[i])
		for _, otherArray in alternateWith do
			table.insert(result, otherArray[i] :: any)
		end
	end

	return result
end

type alternate1 = <A>({A}) -> {A}
type alternate2 = <A, B>({A}, {B}) -> {A | B}
type alternate3 = <A, B, C>({A}, {B}, {C}) -> {A | B | C}
type alternateAll = <T, U, V...>({T}, V...) -> {U}

return (alternate :: any) :: alternate1 & alternate2 & alternate3 & alternateAll


local function any<T>(array: {T}, predicate: (element: T, index: number) -> boolean): boolean
	for index, element in array do
		if predicate(element, index) then
			return true
		end
	end
	return false
end

return any


local function average(array: {number}): number?
	local length = #array
	if length == 0 then
		return nil
	end

	local result = 0

	for _, element in array do
		result += element
	end

	return result / length
end

return average


local function averageBy<T>(array: {T}, map: (T) -> number): number?
	local length = #array
	if length == 0 then
		return nil
	end

	local result = 0

	for _, element in array do
		result += map(element)
	end

	return result / length
end

return averageBy


local function concat<T>(array: {T}, ...: {T}?): {T}
	local result = nil

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

		if concatWith ~= nil and #concatWith > 0 then
			result = result or table.clone(array)

			for _, element in concatWith do
				table.insert(result, element)
			end
		end
	end

	return result or array
end

return concat


local function contains<T>(array: {T}, element: T): boolean
	for _, value in array do
		if value == element then
			return true
		end
	end
	return false
end

return contains


local function deduplicate<T>(array: {T}): {T}
	if #array == 0 then
		return array
	end

	local removeIndexes = {}

	local seenSet = {}

	for index, element in array do
		if seenSet[element] == nil then
			seenSet[element] = true
		else
			table.insert(removeIndexes, index)
		end
	end

	local totalIndexes = #removeIndexes
	if totalIndexes == 0 then
		return array
	else
		local result = table.clone(array)
		for i = totalIndexes, 1, -1 do
			table.remove(result, removeIndexes[i])
		end
		return result
	end
end

return deduplicate


local function deduplicateByKey<T, Comparable>(array: {T}, key: (T) -> Comparable): {T}
	local removeIndexes = {}

	local seenSet = {}

	for index, element in array do
		local elementKey = key(element)
		if seenSet[elementKey] == nil then
			seenSet[elementKey] = true
		else
			table.insert(removeIndexes, index)
		end
	end

	local totalIndexes = #removeIndexes
	if totalIndexes == 0 then
		return array
	else
		local result = table.clone(array)
		for i = totalIndexes, 1, -1 do
			table.remove(result, removeIndexes[i])
		end
		return result
	end
end

return deduplicateByKey


local function filter<T>(array: {T}, predicate: (T, number) -> boolean): {T}
	local new = {}
	for i, element in array do
		if predicate(element, i) then
			table.insert(new, element)
		end
	end
	return new
end

return filter


local function find<T>(array: {T}, predicate: (element: T, index: number) -> boolean, start: number?): T?
	if start == nil or start == 1 then
		for index, element in array do
			if predicate(element, index) then
				return element
			end
		end
	else
		local length = #array
		for i = start, length do
			local element = array[i]
			if predicate(element, i) then
				return element
			end
		end
	end

	return nil
end

return find


local function findIndex<T>(array: {T}, predicate: (element: T, index: number) -> boolean, start: number?): number?
	if start == nil or start == 1 then
		for index, element in array do
			if predicate(element, index) then
				return index
			end
		end
	else
		local length = #array
		for i = start, length do
			local element = array[i]
			if predicate(element, i) then
				return i
			end
		end
	end

	return nil
end

return findIndex


local function findIndexByValue<T>(array: {T}, element: T, start: number?): number?
	return table.find(array, element, start)
end

return findIndexByValue


local function findMap<T, U>(array: {T}, mapFn: (element: T, index: number) -> U?, start: number?): U?
	if start == nil or start == 1 then
		for index, element in array do
			local result = mapFn(element, index)
			if result ~= nil then
				return result
			end
		end
	else
		local length = #array
		for i = start, length do
			local element = array[i]
			local result = mapFn(element, i)
			if result ~= nil then
				return result
			end
		end
	end

	return nil
end

return findMap


local isArray = require("./isArray")

local function flatMap<T, U>(array: {T}, mapFn: (T, number) -> nil | U | {U}): {U}
	local new = {}
	for i, element in array do
		local value = mapFn(element, i)
		if value ~= nil then
			if isArray(value) then
				for _, innerElement in value :: {U} do
					table.insert(new, innerElement)
				end
			else
				table.insert(new, value :: U)
			end
		end
	end
	return new
end

return flatMap


local isArray = require("./isArray")

local function flatten<T>(array: {T | {T}}, depth: number?): {T}
	local actualDepth = if depth == nil then math.huge else depth
	if actualDepth == 0 then
		return array :: {T}
	end

	local new = {}

	for _, element in array do
		if isArray(element) then
			local iter: {T} = if actualDepth > 1 then flatten(element :: {T}) else element :: {T}
			for _, innerElement in iter do
				table.insert(new, innerElement)
			end
		else
			table.insert(new, element :: T)
		end
	end

	return new
end

return flatten


local function fromFn<T>(generator: (number) -> T?): {T}
	local array = {}

	local i = 1
	while true do
		local element = generator(i)
		if element == nil then
			break
		else
			i += 1
			table.insert(array, element)
		end
	end

	return array
end

return fromFn


local function fromTryFn<T>(generator: (number) -> T?): {T}
	local array = {}

	local i = 1
	while true do
		local success, element = pcall(generator, i)
		if success == false or element == nil then
			break
		else
			i += 1
			table.insert(array, element)
		end
	end

	return array
end

return fromTryFn


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

	local tableValue: {[any]: any} = value :: any

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

	local length = #tableValue

	if length == 0 then
		return false
	end

	local expectIndex = 0

	for key in tableValue do
		expectIndex += 1
		if type(key) ~= "number" or key ~= expectIndex then
			return false
		end
	end

	return length == expectIndex
end

return isArray


local function isEmpty(value: {[any]: any}): boolean
	return next(value) == nil
end

return isEmpty


local function map<T, U>(array: {T}, mapFn: (T, number) -> U?): {U}
	local new = {}
	for i, element in array do
		local value = mapFn(element, i)
		if value ~= nil then
			table.insert(new, value)
		end
	end
	return new
end

return map


local function maximum(array: {number}, initialValue: number?): number
	if initialValue == nil then
		if #array == 0 then
			error("initial value must be provided to Array.maximum when the array is empty")
		end
		initialValue = array[1]
	end

	local accumulator = initialValue :: number
	for _, element in array do
		accumulator = math.max(accumulator, element)
	end
	return accumulator
end

return maximum


local function maximumBy<T>(array: {T}, map: (T) -> number, initialValue: number?): number
	if initialValue == nil then
		if #array == 0 then
			error("initial value must be provided to Array.maximumBy when the array is empty")
		end
		initialValue = map(array[1])
	end

	local accumulator: number = initialValue :: number
	for _, element in array do
		accumulator = math.max(accumulator, map(element))
	end
	return accumulator
end

return maximumBy


local function minimum(array: {number}, initialValue: number?): number
	if initialValue == nil then
		if #array == 0 then
			error("initial value must be provided to Array.minimum when the array is empty")
		end
		initialValue = array[1]
	end

	local accumulator = initialValue :: number
	for _, element in array do
		accumulator = math.min(accumulator, element)
	end
	return accumulator
end

return minimum


local function minimumBy<T>(array: {T}, map: (T) -> number, initialValue: number?): number
	if initialValue == nil then
		if #array == 0 then
			error("initial value must be provided to Array.minimumBy when the array is empty")
		end
		initialValue = map(array[1])
	end

	local accumulator: number = initialValue :: number
	for _, element in array do
		accumulator = math.min(accumulator, map(element))
	end
	return accumulator
end

return minimumBy


local function partition<T>(array: {T}, predicate: (T, number) -> boolean): ({T}, {T})
	local trueValues = {}
	local falseValues = {}
	for i, element in array do
		if predicate(element, i) then
			table.insert(trueValues, element)
		else
			table.insert(falseValues, element)
		end
	end
	return trueValues, falseValues
end

return partition


local function pop<T>(array: {T}, count: number?): {T}
	local actualCount = count or 1

	if actualCount == 1 then
		local new = table.clone(array)
		table.remove(new)
		return new
	elseif actualCount == 0 then
		return array
	else
		local length = #array

		local remaining = length - actualCount

		if remaining <= 0 then
			return {}
		elseif remaining > actualCount then
			local new = table.clone(array)

			for _ = 1, actualCount do
				table.remove(new)
			end

			return new
		else
			local new = {}

			for i = 1, remaining do
				new[i] = array[i]
			end
			return new
		end
	end
end

return pop


local function popFirst<T>(array: {T}, count: number?): {T}
	local actualCount = count or 1

	if actualCount == 1 then
		local new = table.clone(array)
		table.remove(new, 1)
		return new
	elseif actualCount == 0 then
		return array
	else
		local length = #array

		local remaining = length - actualCount

		if remaining <= 0 then
			return {}
		elseif remaining > actualCount then
			local new = table.clone(array)

			for _ = 1, actualCount do
				table.remove(new, 1)
			end

			return new
		else
			local new = {}

			for i = 1 + actualCount, length do
				table.insert(new, array[i])
			end

			return new
		end
	end
end

return popFirst


local function product(array: {number}, initialValue: number?): number
	local result = if initialValue == nil then 1 else initialValue

	for _, element in array do
		result *= element
	end

	return result
end

return product


local function productBy<T>(array: {T}, map: (T) -> number, initialValue: number?): number
	local result = if initialValue == nil then 1 else initialValue

	for _, element in array do
		result *= map(element)
	end

	return result
end

return productBy


local function push<T>(array: {T}, ...: T?): {T}
	local newArray = table.clone(array)

	for i = 1, select("#", ...) do
		local element = select(i, ...)
		if element ~= nil then
			table.insert(newArray, element)
		end
	end

	return newArray
end

return push


local function range<T>(array: {T}, from: number, to: number?): {T}
	local length = #array
	if length == 0 then
		return array
	end
	local actualTo = if to == nil then length else to

	local normalizedFrom = if from < 0 then 1 + (from + length) else from
	local normalizedTo = if actualTo < 0 then 1 + (actualTo + length) else actualTo

	local result = {}

	if normalizedTo >= normalizedFrom then
		for i = math.max(normalizedFrom, 1), math.min(normalizedTo, length) do
			table.insert(result, array[i])
		end
	else
		for i = math.min(normalizedFrom, length), math.max(normalizedTo, 1), -1 do
			table.insert(result, array[i])
		end
	end

	return result
end

return range


local function reduce<T, A>(array: {T}, reducer: (accumulator: A, value: T, index: number) -> A, accumulator: A?): A
	if accumulator == nil then
		for index, value in array do
			if index == 1 then
				accumulator = value :: any
			else
				accumulator = reducer(accumulator :: A, value, index)
			end
		end
	else
		for index, value in array do
			accumulator = reducer(accumulator, value, index)
		end
	end
	return accumulator :: A
end

type reduceFromFirstValue = <T>(array: {T}, reducer: (accumulator: T, value: T, index: number) -> T) -> T?
type reduceWithAccumulator = <T, A>(
	array: {T},
	reducer: (accumulator: A, value: T, index: number) -> A,
	accumulator: A
) -> A

return (reduce :: any) :: reduceFromFirstValue & reduceWithAccumulator


local removeSortedIndexes = require("./removeSortedIndexes")
local sort = require("./sort")

local function removeIndexes<T>(array: {T}, indexes: {number}): {T}
	local removeLength = #indexes
	local arrayLenth = #array

	if removeLength == 0 or arrayLenth == 0 then
		return array
	end

	local sortedIndexes = sort(indexes)

	return removeSortedIndexes(array, sortedIndexes)
end

return removeIndexes


local function removeSortedIndexes<T>(array: {T}, indexes: {number}): {T}
	local removeLength = #indexes
	local arrayLenth = #array

	if removeLength == 0 or arrayLenth == 0 then
		return array
	end

	local startFrom = nil
	for i = removeLength, 1, -1 do
		local index = indexes[i]
		if index <= arrayLenth then
			startFrom = index
			break
		end
	end

	if startFrom == nil then
		return array
	else
		local cloned = table.clone(array)
		local length = arrayLenth

		local previous = nil
		for i = startFrom, 1, -1 do
			local index = indexes[i]
			if index ~= previous and index <= length then
				length -= 1
				table.remove(cloned, index)
				previous = index
			end
		end

		return cloned
	end
end

return removeSortedIndexes


local function removeValues<T>(array: {T}, ...: T?): {T}
	local removeLength = select("#", ...)
	local arrayLenth = #array

	if removeLength == 0 or arrayLenth == 0 then
		return array
	end

	local removeIndexes = {}
	for index, element in array do
		for i = 1, removeLength do
			local value: T? = select(i, ...)

			if value ~= nil and element == value then
				table.insert(removeIndexes, index)
			end
		end
	end

	local removeIndexesLength = #removeIndexes

	if removeIndexesLength == 0 then
		return array
	elseif removeIndexesLength == arrayLenth then
		return {}
	else
		local cloned = table.clone(array)

		for i = removeIndexesLength, 1, -1 do
			table.remove(cloned, removeIndexes[i])
		end

		return cloned
	end
end

return removeValues


local function reverse<T>(array: {T}): {T}
	local length = #array

	if length < 2 then
		return array
	end

	local newArray = table.clone(array)

	for i = 1, math.floor(length / 2) do
		local swapRight = length - i + 1

		newArray[i], newArray[swapRight] = newArray[swapRight], newArray[i]
	end

	return newArray
end

return reverse


local function sort<T>(array: {T}, comparator: ((T, T) -> boolean)?): {T}
	if #array <= 1 then
		return array
	end
	local result = table.clone(array)
	table.sort(result, comparator)
	return result
end

return sort


local map = require("./map")

type KeyElement<K, V> = {key: K, element: V}

local function sortByKey<T, Comparable>(array: {T}, key: (T) -> Comparable): {T}
	if #array <= 1 then
		return array
	end
	local result: {KeyElement<Comparable, T>} = map(array, function(element)
		return {
			key = key(element);
			element = element;
		}
	end)

	table.sort(result, function(a: any, b: any)
		return a.key < b.key
	end)

	for index, element in result do
		result[index] = element.element :: any
	end

	return result :: any
end

return sortByKey


local function stepBy<T>(array: {T}, step: number): {T}
	local length = #array

	if step == 1 then
		return array
	elseif step == -1 then
		local newArray = table.clone(array)

		for i = 1, math.floor(length / 2) do
			local swapRight = length - i + 1

			newArray[i], newArray[swapRight] = newArray[swapRight], newArray[i]
		end

		return newArray
	elseif step > 1 then
		local result = {}

		for i = 1, length, step do
			table.insert(result, array[i])
		end

		return result
	elseif step < -1 then
		local result = {}

		for i = length, 1, step do
			table.insert(result, array[i])
		end

		return result
	else
		error(`invalid step value '{step}' passed to Array.stepBy`)
	end
end

return stepBy


local function sum(array: {number}, initialValue: number?): number
	local result = if initialValue == nil then 0 else initialValue

	for _, element in array do
		result += element
	end

	return result
end

return sum


local function sumBy<T>(array: {T}, map: (T) -> number, initialValue: number?): number
	local result = if initialValue == nil then 0 else initialValue

	for _, element in array do
		result += map(element)
	end

	return result
end

return sumBy


local function takeWhile<T>(array: {T}, predicate: (element: T, index: number) -> boolean, start: number?): {T}
	local arrayLength = #array

	if arrayLength == 0 then
		return array
	end

	local bound = arrayLength + 1
	local actualStart = if start == nil then 1 else start
	if actualStart == 1 then
		for index, element in array do
			if not predicate(element, index) then
				bound = index
				break
			end
		end
	else
		local length = #array
		for i = actualStart, length do
			local element = array[i]
			if not predicate(element, i) then
				bound = i
				break
			end
		end
	end

	if actualStart == bound then
		return {}
	elseif actualStart == 1 and bound > arrayLength then
		return array
	end

	local toInsert = bound - actualStart

	if actualStart == 1 and toInsert < arrayLength / 2 then
		local result = table.clone(array)

		for _ = 1, arrayLength - bound do
			table.remove(result)
		end

		return result
	else
		local result = {}

		for i = actualStart, bound - 1 do
			table.insert(result, array[i])
		end

		return result
	end
end

return takeWhile


local reduce = require("./reduce")

local function zip<T>(array: {T}, ...: {T}): {{T}}
	local alternateWith = {...}

	local minimumLength = reduce(alternateWith, function(minimum: number, value)
		return math.min(minimum, #value)
	end, #array)

	if minimumLength == 0 then
		return {}
	end

	local result = {}

	for i = 1, minimumLength do
		local entry = {array[i]}
		for _, otherArray in alternateWith do
			table.insert(entry, otherArray[i])
		end
		table.insert(result, entry)
	end

	return result
end

type zip1 = <A>({A}) -> {{A}}
type zip2 = <A, B>({A}, {B}) -> {{A | B}}
type zip3 = <A, B, C>({A}, {B}, {C}) -> {{A | B | C}}
type zipAll = <T, U>({T}, ...{U}) -> {{T | U}}

return (zip :: any) :: zip1 & zip2 & zip3 & zipAll


local setType = require("@self/set/type")

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

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

return Disk


local function count<K, V>(map: {[K]: V}): number
	local length = 0
	for _ in map do
		length += 1
	end
	return length
end

return count


local function entries<K, V>(map: {[K]: V}, keyIndex: any?, valueIndex: any?): {{[any]: K | V}}
	local useKeyIndex = if keyIndex == nil then 1 else keyIndex
	local useValueIndex = if valueIndex == nil then useKeyIndex + 1 else valueIndex

	local array = {}

	for key, value in map do
		table.insert(array, {
			[useKeyIndex] = key :: K | V;
			[useValueIndex] = value;
		})
	end

	return array
end

type EntriesFn =
	(<K, V>(map: {[K]: V}) -> {{K | V}})
	& (<K, V, T, U>(map: {[K]: V}, keyIndex: T, valueIndex: U) -> {{[T | U]: K | V}})

return entries :: EntriesFn


local function invert<K, V>(map: {[K]: V}): {[V]: K}
	if next(map) == nil then
		return map :: any
	end

	local result = {}
	for key, value in map do
		result[value] = key
	end
	return result
end

return invert


local function keys<K, V>(map: {[K]: V}): {K}
	local result = {}
	for key in map do
		table.insert(result, key)
	end
	return result
end

return keys


local function mapEntries<K, V, K2, V2>(map: {[K]: V}, mapFn: (key: K, value: V) -> (K2?, V2?)): {[K2]: V2}
	local result = {}

	for key, value in map do
		local newKey, newValue = mapFn(key, value)
		if newKey ~= nil and newValue ~= nil then
			result[newKey] = newValue
		end
	end

	return result
end

return mapEntries


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

local function mapValues<K, V, W>(map: {[K]: V}, mapFn: (value: V, key: K) -> W?): {[K]: W}
	local result = {}

	for key, value in map do
		local newValue = mapFn(value, key)
		if newValue ~= nil and newValue ~= (None :: any) then
			result[key] = newValue
		end
	end

	return result
end

return mapValues


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

local function merge<T, U...>(...: U...): any
	local first = nil
	local firstIndex = nil
	local length = select("#", ...)

	for i = 1, length do
		local mergeMap = select(i, ...)

		if mergeMap ~= nil then
			firstIndex = i
			first = mergeMap
			break
		end
	end

	if first == nil then
		error("attempt to merge only nil values")
	end

	if firstIndex == length then
		return first
	end

	local result = nil

	for i = firstIndex + 1, length do
		local mergeMap = select(i, ...)

		if mergeMap ~= nil then
			if result == nil then
				result = table.clone(first)
				setmetatable(result, nil)
			end

			local metatable = getmetatable(mergeMap)

			if metatable ~= nil and type(metatable.__call) == "function" then
				for key, value in pairs(mergeMap) do
					if value == None then
						result[key] = nil
					else
						result[key] = value
					end
				end
			else
				for key, value in mergeMap do
					if value == None then
						result[key] = nil
					else
						result[key] = value
					end
				end
			end
		end
	end

	return result or first
end

type merge1 = <A>(A) -> A
type merge2 = <A, B>(A, B) -> A & B
type merge3 = <A, B, C>(A, B, C) -> A & B & C
type merge4 = <A, B, C, D>(A, B, C, D) -> A & B & C & D
type merge5 = <A, B, C, D, E>(A, B, C, D, E) -> A & B & C & D & E
type mergeAll = <T, U, V...>(T, V...) -> U

return (merge :: any) :: merge1 & merge2 & merge3 & merge4 & merge5 & mergeAll


local isEmpty = require("./isEmpty")

local function remove<K, V>(map: {[K]: V}, ...: K?)
	local removeLength = select("#", ...)

	if removeLength == 0 or isEmpty(map) then
		return map
	else
		local result = nil

		for i = 1, removeLength do
			local key: K = select(i, ...)

			if key ~= nil and map[key] ~= nil then
				result = result or table.clone(map)
				result[key] = nil
			end
		end

		return result or map
	end
end

return remove


local function values<K, V>(map: {[K]: V}): {V}
	local result = {}
	for _, value in map do
		table.insert(result, value)
	end
	return result
end

return values


local type = require("./type")

type Set<T> = type.Set<T>

local function fromArray<T>(array: {T}): Set<T>
	local set: Set<T> = {}
	for _, element in array do
		set[element] = true
	end
	return set
end

return fromArray


--!optimize 2

local Prefix = require("./Prefix")
local sysPath = require("./sys/path")

local MAIN_SEPARATOR_STR = sysPath.MAIN_SEPARATOR_STR

type Prefix = Prefix.Prefix

export 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,
}

export type Component = ComponentEnum & ComponentCommon

type ComponentStatic = Component & {
	normal: (value: string) -> Component,
	rootDir: () -> Component,
	curDir: () -> Component,
	parentDir: () -> Component,
	prefix: (raw: string, prefix: Prefix) -> Component,
}

local Component: ComponentStatic = {} :: any
local ComponentMetatable = {
	__index = Component;
	__tostring = function(self)
		return self:toString()
	end;
	__eq = function(self: Prefix, other: Prefix): boolean
		return self:equal(other)
	end;
}

function Component.normal(value: string): Component
	local self: ComponentEnum = {type = "normal", value = value}

	return setmetatable(self, ComponentMetatable) :: any
end

function Component.rootDir(): Component
	local self: ComponentEnum = {type = "rootDir"}

	return setmetatable(self, ComponentMetatable) :: any
end

function Component.curDir(): Component
	local self: ComponentEnum = {type = "curDir"}

	return setmetatable(self, ComponentMetatable) :: any
end

function Component.parentDir(): Component
	local self: ComponentEnum = {type = "parentDir"}

	return setmetatable(self, ComponentMetatable) :: any
end

function Component.prefix(raw: string, prefix: Prefix): Component
	local self: PrefixComponent = {type = "prefix", raw = raw, parsed = prefix}

	return setmetatable(self, ComponentMetatable) :: any
end

function Component:toString(): string
	local componentType = self.type
	return if componentType == "normal"
		then (self :: NormalComponent & ComponentCommon).value
		elseif componentType == "curDir" then "."
		elseif componentType == "parentDir" then ".."
		elseif componentType == "rootDir" then MAIN_SEPARATOR_STR
		else (self :: PrefixComponent & ComponentCommon).raw
end

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

	if selfType ~= other.type then
		return false
	end

	if selfType == "normal" then
		return (self :: NormalComponent & ComponentCommon).value == (other :: NormalComponent & ComponentCommon).value
	elseif selfType == "prefix" then
		return (self :: PrefixComponent & ComponentCommon).parsed:equal(
			(other :: PrefixComponent & ComponentCommon).parsed
		)
	end

	return true
end

return Component


--!nolint LocalShadow
--!optimize 2
--# selene:allow(shadowing)

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 + #(self :: any).value
	elseif selfType == "VerbatimUNC" then
		local data: {hostName: string, shareName: string} = self :: any
		local shareNameLen = #data.shareName
		return 8 + #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 = #data.shareName
		return 2 + #data.hostName + if shareNameLen > 0 then 1 + shareNameLen else 0
	elseif selfType == "DeviceNS" then
		return 4 + #(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
--# selene:allow(shadowing)

local function FromFunction<T>(generator: (index: number) -> T?): {T}
	local array = {}
	local length = 0

	while true do
		local value = generator(length + 1)
		if value == nil then
			break
		end

		length += 1
		array[length] = value
	end

	return array
end

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 FromFunction(function(): T?
		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("@self/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 table.freeze({
	Path = (Path :: never) :: {
		new: (path: string) -> Path,
		from: (path: AsPath) -> Path,
		is: (value: unknown) -> boolean,
	};
})


--!optimize 2

local Component = require("./Component")

type Component = Component.Component

type ComponentIterator = {next: (self: ComponentIterator) -> Component?}
type Clone<T> = {clone: (T) -> T}

local function iterAfter<T>(
	iter: T & ComponentIterator & Clone<ComponentIterator>,
	prefix: T & ComponentIterator
): (T & ComponentIterator)?
	-- print(">>>> call iterAfter >>>>")
	while true do
		-- print("  >> begin loop")
		local iterNext = iter:clone()
		local x = iterNext:next()
		local y = prefix:next()

		-- print('x =', x)
		-- print('y =', y)

		if x ~= nil and y ~= nil then
			if not x:equal(y) then
				return nil
			end
		elseif y == nil then
			return iter
		else
			return nil
		end
		iter = iterNext :: any
	end
end

return iterAfter


--!nolint LocalShadow
--!optimize 2
--# selene:allow(shadowing)

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
--# selene:allow(shadowing)

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