--!native
--!optimize 2

---- Imports ----

local process = require("@lune/process")
local stdio = require("@lune/stdio")

local Compile = require("@self/Utility/Compile")
local Watch = require("@self/Utility/Watch")

---- Settings ----

type Argument = {
	Hidden: true?,
	Aliases: { string },
	Description: string,
}

local ARGUMENTS: { [string]: Argument } = {
	Help = {
		Aliases = { "-h", "--help" },
		Description = "Print help information",
	},
	Version = {
		Aliases = { "-v", "--version" },
		Description = "Print version information",
	},
	Watch = {
		Aliases = { "-w", "--watch" },
		Description = "Watch [CONFIG] for changes, automatically recompile upon one occuring",
	},
	Silent = {
		Aliases = { "-q", "--quiet" },
		Description = "Silence program output",
	},
	Compact = {
		Aliases = { "-c", "--compact" },
		Description = "Compacts error output, the full message is still printed out after a compacted version.",
	},
	YesToAll = {
		Aliases = { "-y", "--yes" },
		Description = "Accept all prompts",
	},
	OutputAst = {
		Hidden = true,
		Aliases = { "--ast" },
		Description = "Output the AST of [CONFIG]",
	},
	Statistics = {
		Hidden = true,
		Aliases = { "-S", "--stats" },
		Description = "Output compilation time statistics",
	},
}

---- Functions -----

local function Style(Text: string, Style: stdio.Style): string
	return `{stdio.style(Style)}{Text}{stdio.style("reset")}`
end

local function Color(Text: string, Color: stdio.Color): string
	return `{stdio.color(Color)}{Text}{stdio.color("reset")}`
end

local function PrintHelpMessage()
	print(Color(Style("USAGE:", "bold"), "yellow"))
	print("\tblink.exe [CONFIG] [OPTIONS]")
	print(Color(Style("OPTIONS:", "bold"), "yellow"))

	local Sorted: { Argument } = {}
	for _, Argument in ARGUMENTS do
		if not Argument.Hidden then
			table.insert(Sorted, Argument)
		end
	end

	table.sort(Sorted, function(a, b)
		return a.Aliases[1] < b.Aliases[1]
	end)

	for _, Argument in Sorted do
		print(
			`\t{stdio.color("green")}{table.concat(Argument.Aliases, ", ")}{stdio.color("reset")}\t{Argument.Description}`
		)
	end
end

---- Main ----

print(`{Color("Blink", "green")} {_G.VERSION or "DEBUG"}`)

local RawArguments = process.args
if #RawArguments < 1 then
	PrintHelpMessage()
	return nil
end

local Path = RawArguments[1]

--> Parse optional arguments
local Arguments = {}
for Key, Argument in ARGUMENTS do
	local IsSupplied = false
	for _, Alias in Argument.Aliases do
		if table.find(RawArguments, Alias) ~= nil then
			IsSupplied = true
			break
		end
	end

	Arguments[Key] = IsSupplied
end

if Arguments.Help then
	PrintHelpMessage()
	return nil
end

if Arguments.Version then
	return nil
end

if not Arguments.Watch then
	local CompileOptions: Compile.CompileOptions = {
		Debug = (_G.RELEASE == nil) or Arguments.Statistics,
		Silent = Arguments.Silent,
		Compact = Arguments.Compact,
		YesToEverything = Arguments.YesToAll,
		OutputAbstractSyntaxTree = Arguments.OutputAst,
	}

	Compile.Compile(Path, CompileOptions)
else
	Watch.Watch(Path)
end

return nil


type BranchType = "Conditional" | "Default"
type EqualityOperator = "Not" | "Equals" | "Greater" | "Less" | "GreaterOrEquals" | "LessOrEquals"

type Method = "Read" | "Allocate" | "None"
type Operation = {
	Method: Method,
	Bytes: number,
	Counts: number,
	Variable: string?,
}

local DEFAULT_UNIQUE = "BLOCK_START"
local ARRAY_UNIQUE = "ARRAY_START"

local Operators = {
	Not = "~=",
	Equals = "==",
	Greater = ">",
	Less = "<",
	GreaterOrEquals = ">=",
	LessOrEquals = "<=",
}

local Block = {}
Block.__index = Block

export type Block = typeof(setmetatable(
	{} :: {
		Unique: string,
		Parent: Block?,
		Indent: number,
		Cursor: number,
		Unsafe: boolean,
		Content: { string },
		Operation: Operation,
		Variables: { [string]: boolean },
		OperationOffset: number,
	},
	Block
))

function Block.new<Parent>(Parent: Block?, Unique: string?): Block
	local Indent = (Parent and Parent.Indent + 1 or 1)
	return setmetatable({
		Unique = Unique or DEFAULT_UNIQUE,
		Parent = Parent,
		Indent = Indent,
		Cursor = 1,
		Unsafe = false,
		Content = table.create(64),
		Operation = {
			Method = "None",
			Bytes = 0,
			Counts = 0,
		},
		Variables = {},
		OperationOffset = 0,
	}, Block)
end

function Block.DisableOperationOptimisations(self: Block): Block
	self.Unsafe = true
	return self
end

function Block._operation(self: Block, Method: Method, Bytes: number): string
	local Operation = self.Operation
	if Operation.Method ~= "None" and Operation.Method ~= Method then
		error(`Block can't both Allocate and Read`)
	end

	if self.Unsafe then
		local Counts = Operation.Counts
		local Variable = `OFFSET_{Counts}`

		Operation.Counts += 1
		self:Line(`local {Variable} = {Method}({Bytes})`)

		return Variable
	end

	local Offset = Operation.Bytes
	Operation.Bytes += Bytes
	Operation.Method = Method

	if self.Unique ~= DEFAULT_UNIQUE then
		self:Comment(`{Method} {Bytes}`)
		local OperationOffset = `OPERATION_OFFSET_{self.OperationOffset}`
		self.OperationOffset += 1
		self:Line(`local {OperationOffset} = {self.Unique}`)
		self:Line(`{self.Unique} += {Bytes}`)
		return OperationOffset
	end

	return `{self.Unique} + {Offset}`
end

function Block.Read(self: Block, Bytes: number): string
	return self:_operation("Read", Bytes)
end

function Block.Allocate(self: Block, Bytes: number): string
	return self:_operation("Allocate", Bytes)
end

function Block._lineFront(self: Block, Text: string, Indent: number): Block
	table.insert(self.Content, self.Cursor, `{string.rep("\t", Indent or self.Indent)}{Text}\n`)
	return self
end

function Block._appendOperations(self: Block): Block
	local Operation = self.Operation
	local Variable = Operation.Variable
	if Operation.Method == "None" then
		return self
	end

	local Append = `local {self.Unique} = {Operation.Method}({Operation.Bytes}{Variable and ` * {Variable}` or ""})`

	self:_lineFront(Append, self.Unique ~= DEFAULT_UNIQUE and self.Indent - 1 or self.Indent)
	self:_lineFront(`-- {Operation.Method} BLOCK: {Operation.Bytes} bytes`)

	return self
end

function Block.Declare(self: Block, Variable: string, Initialize: boolean?): string
	local Block = self
	local Declared = false
	local IsFieldAccess = (string.find(Variable, "[%.%[]", 1) ~= nil)

	--> Ignore field acceses
	if IsFieldAccess then
		return Variable
	end

	--> Search for variable declaration in block hierarchy
	while Block do
		if Block.Variables[Variable] then
			Declared = true
			break
		end

		Block = Block.Parent
	end

	if Declared then
		return Variable
	end

	--> Declare variable within current block
	self.Variables[Variable] = true

	if Initialize then
		self:Line(`local {Variable}`)
	end

	return `local {Variable}`
end

function Block.EmptyDeclare(self: Block, Variable: string)
	self.Variables[Variable] = true
end

function Block.Advance(self: Block, Offset: number): Block
	self.Cursor += Offset
	return self
end

function Block.Line(self: Block, Text: string, Indent: number?): Block
	table.insert(self.Content, `{string.rep("\t", Indent or self.Indent)}{Text}\n`)
	return self
end

function Block.Character(self: Block, Character: string): Block
	table.insert(self.Content, Character)
	return self
end

function Block.Comment(self: Block, Content: string): Block
	self:Line(`-- {Content}`)
	return self
end

function Block.Lines(self: Block, Lines: { string }, Indent: number?): Block
	local Indent = Indent or 0

	--> FAST PATH
	if Indent == 0 then
		table.move(Lines, 1, #Lines, #self.Content + 1, self.Content)
		return self
	end

	local Indentation = string.rep("\t", Indent)

	for Index, Line in Lines do
		table.insert(self.Content, `{Indentation}{Line}`)
	end

	return self
end

function Block.Multiline(self: Block, Content: string, Indent: number?): Block
	local Lines = string.split(Content, "\n")
	for Index, Line in Lines do
		table.insert(self.Content, `{string.rep("\t", Indent or 0)}{Line}\n`)
	end

	return self
end

function Block.Loop(self: Block, Counter: string, Length: string): Block
	local Loop = Block.new(self, `{ARRAY_UNIQUE}_{self.Indent}`)
	Loop:Line(`for {Counter}, {Length} do`, Loop.Indent - 1)
	Loop.Operation.Variable = Length
	return Loop
end

function Block.While(self: Block, Condition: string): Block
	self:Line(`while ({Condition}) do`)
	return Block.new(self)
end

function Block.Iterator(self: Block, Key: string, Value: string, Iterator: string): Block
	self:Line(`for {Key}, {Value} in {Iterator} do`)
	return Block.new(self)
end

function Block.Compare(self: Block, Left: string, Right: string, Operator: EqualityOperator): Block
	self:Line(`if {Left} {Operators[Operator]} {Right} then`)
	return Block.new(self)
end

function Block.Branch(
	self: Block,
	Branch: BranchType,
	Left: string?,
	Right: string?,
	Operator: EqualityOperator?
): Block
	local Parent = self.Parent
	assert(Parent, "Cannot branch the top level block.")

	--> Push previous branch content
	self:_appendOperations()
	Parent:Lines(self.Content)

	--> Create new branch
	if Branch == "Conditional" then
		Parent:Line(`elseif {Left} {Operators[Operator]} {Right} then`)
	else
		Parent:Line(`else`)
	end

	return Block.new(Parent)
end

function Block.Return(self: Block, Return: string): Block
	self:Line(`return {Return}`)
	return self
end

function Block.End(self: Block): Block
	self:_appendOperations()

	local Parent = self.Parent
	if Parent then
		Parent:Lines(self.Content)
		Parent:Line("end")
		return Parent
	end

	self:Line("end", 0)
	return self
end

function Block.Wrap(self: Block, Front: string, Back: string): Block
	local First = self.Content[1]
	self.Content[1] = `{Front}{First}`

	local Last = self.Content[#self.Content]
	self.Content[#self.Content] = `{string.gsub(Last, "\n", "")}{Back}\n`

	return self
end

function Block.Unwrap(self: Block)
	return table.concat(self.Content)
end

local Function = {}
Function.__index = Function
setmetatable(Function, Block)

function Function.new(Name: string, Arguments: string, Return: string?, IsInlined: boolean?, Localised: boolean?): Block
	local Block = Block.new(nil)
	setmetatable(Block, Function)

	local Suffix = Return and `: {Return}` or ""
	Block:Advance(1)

	if IsInlined then
		Block:Line(`{Name} = function({Arguments}){Suffix}`, 0)
	elseif Localised then
		Block:Line(`local function {Name}({Arguments}){Suffix}`, 0)
	else
		Block:Line(`function {Name}({Arguments}){Suffix}`, 0)
	end

	return Block
end

local Connection = {}
Connection.__index = Connection
setmetatable(Connection, Block)

function Connection.new(Signal: string, Arguments: string): Block
	local Block = Block.new()
	setmetatable(Block, Connection)
	Block:Advance(1)
	Block:Line(`{Signal}:Connect(function({Arguments})`, 0)
	return Block
end

function Connection.End(self: Block, Return: string?): Block
	self:Line("end)", 0)
	return self
end

return {
	Block = Block.new,
	Function = Function.new,
	Connection = Connection.new,
}


--!native
--!optimize 2

local Blocks = require("./Blocks")
local Parser = require("../Parser")

type Block = Blocks.Block
type Writer = (Value: string, Block: Block, Range: NumberRange?, Components: { string }?) -> ()
type Reader = (Variable: string, Block: Block, Range: NumberRange?, Components: { string }?) -> ()
type Generate = (
	Declaration: Parser.Primitive,
	Variable: string?,
	Read: Block,
	Write: Block,
	WriteValidations: boolean?
) -> ()

type TypePrefab = {
	Read: Reader,
	Write: Writer,
}

type AssertPrefab = {
	Lower: string,
	Upper: string,
	Exact: string?,
}

type AssertGenerator = (Variable: string, ...any) -> AssertPrefab

local SEND_BUFFER = "SendBuffer"
local RECIEVE_BUFFER = "RecieveBuffer"
local SEND_POSITION = "SendOffset"

local BYTE = 1
local SHORT = 2
local INTEGER = 4
local HALF_FLOAT = 2
local FLOAT = 4
local DOUBLE = 8

local Types = {}
local Asserts = {
	number = function(Variable: string, Min: number, Max: number): AssertPrefab
		return {
			Lower = `if {Variable} < {Min} then error(\`Expected "{Variable}" to be larger than {Min}, got \{{Variable}} instead.\`) end`,
			Upper = `if {Variable} > {Max} then error(\`Expected "{Variable}" to be smaller than {Max}, got \{{Variable}} instead.\`) end`,
			Exact = `if {Variable} ~= {Max} then error(\`Expected "{Variable}" to be equal to {Max}, got \{{Variable}\} instead.\`) end`,
		}
	end,

	string = function(Variable: string, Min: number, Max: number): AssertPrefab
		return {
			Lower = `if #{Variable} < {Min} then error(\`Expected length of "{Variable}" to be larger than {Min}, got \{#{Variable}} instead.\`) end`,
			Upper = `if #{Variable} > {Max} then error(\`Expected length of "{Variable}" to be smaller than {Max}, got \{#{Variable}} instead.\`) end`,
			Exact = `if #{Variable} ~= {Max} then error(\`Expected length of "{Variable}" to equal to {Max}, got \{#{Variable}} instead.\`) end`,
		}
	end,

	vector = function(Variable: string, Min: number, Max: number): AssertPrefab
		return {
			Lower = `if {Variable}.Magnitude < {Min} then error(\`Expected magnitude of "{Variable}" to be larger than {Min}, got \{{Variable}.Magnitude} instead.\`) end`,
			Upper = `if {Variable}.Magnitude > {Max} then error(\`Expected magnitude of "{Variable}" to be smaller than {Max}, got \{{Variable}.Magnitude} instead.\`) end`,
			Exact = `if {Variable}.Magnitude ~= {Max} then error(\`Expected magnitude of "{Variable}" to equal to {Max}, got \{{Variable}.Magnitude} instead.\`) end`,
		}
	end,

	buffer = function(Variable: string, Min: number, Max: number): AssertPrefab
		return {
			Lower = `if buffer.len({Variable}) < {Min} then error(\`Expected size of "{Variable}" to be larger than {Min}, got \{buffer.len({Variable})} instead.\`) end`,
			Upper = `if buffer.len({Variable}) > {Max} then error(\`Expected size of "{Variable}" to be smaller than {Max}, got \{buffer.len({Variable})} instead.\`) end`,
			Exact = `if buffer.len({Variable}) ~= {Max} then error(\`Expected size of "{Variable}" to equal to {Max}, got \{buffer.len({Variable})} instead.\`) end`,
		}
	end,

	Instance = function(Variable: string, Class: string): AssertPrefab
		local TypeAssertion =
			`if typeof({Variable}) ~= "Instance" then error(\`Expected an Instance, got \{typeof({Variable})} instead.\`) end`
		return {
			Lower = `error("Something has gone wrong")`,
			Upper = `error("Something has gone wrong")`,
			Exact = if Class == "Instance"
				then TypeAssertion
				else `{TypeAssertion}\nif not {Variable}:IsA("{Class}") then error(\`Expected an Instance of type "{Class}", got "\{{Variable}.ClassName}" instead.\`) end`,
		}
	end,
}
local Structures = {
	Array = nil :: any,
}
local Primitives: { [string]: {
	Type: string | (Declaration: Parser.Primitive) -> string,
	Generate: Generate,
} } = {}

local NumberTypeBounds = {
	{ Bits = 8, Maximum = 255, Type = "u8" },
	{ Bits = 16, Maximum = 65535, Type = "u16" },
	{ Bits = 32, Maximum = 4294967295, Type = "u32" },
}

local function GetTypeToUse(UpperSize: number): TypePrefab
	for _, NumberType in NumberTypeBounds do
		if NumberType.Maximum >= UpperSize then
			return Types[NumberType.Type]
		end
	end

	error("Value is too large to be written to a buffer.")
end

local function GetTypeToUseByBits(UpperBits: number): TypePrefab
	for _, NumberType in NumberTypeBounds do
		if NumberType.Bits >= UpperBits then
			return Types[NumberType.Type]
		end
	end

	error("Value is too large to be written to a buffer.")
end

local function GeneratePrimitivePrefab(Type: string, Prefab: TypePrefab, AssertGenerator: AssertGenerator?): Generate
	return function(
		Declaration: Parser.Primitive,
		Variable: string?,
		Read: Block,
		Write: Block,
		WriteValidations: boolean?
	)
		local Value = Declaration.Value
		local Tokens = Declaration.Tokens
		local Variable = Variable or "Value"

		local Range = Value.Range
		local Class = Value.Class
		local Components = Value.Components
		local Primitive = Tokens.Primitive.Value
		local IsVariableSize = (Range and Range.Max ~= Range.Min)

		local function GenerateValidation(Block: Block, Variable: string)
			if Block == Write and WriteValidations ~= true then
				return
			end

			if Block == Write and Type ~= "any" then
				Block:Compare(`typeof({Variable})`, `"{Type}"`, "Not")
					:Line(`error(\`Expected {Variable} to be of type {Type}, got \{type({Variable})} instead.\`)`)
					:End()
			end

			if not AssertGenerator then
				return
			end

			if Range then
				local Assert = AssertGenerator(Variable, Range.Min, Range.Max)
				if not IsVariableSize and Assert.Exact then
					Block:Line(Assert.Exact)
				else
					Block:Line(Assert.Lower)
					Block:Line(Assert.Upper)
				end
			elseif Primitive == "Instance" and Class then
				local Assert = AssertGenerator(Variable, Class)
				if Block == Write then
					Block:Compare(Variable, "nil", "Not"):Multiline(Assert.Exact, Block.Indent + 1):End()
				else
					Block:Multiline(Assert.Exact, Block.Indent)
				end
			end
		end

		GenerateValidation(Write, Variable)
		Prefab.Read(Variable, Read, Range, Components)
		Prefab.Write(Variable, Write, Range, Components)
		GenerateValidation(Read, Variable)
	end
end

local function GenerateNumberGenerator(Type: string, Size: number): (TypePrefab, { Type: string, Generate: Generate })
	local Prefab = {
		Read = function(Variable: string, Block: Block)
			Block:Line(`{Block:Declare(Variable)} = buffer.read{Type}({RECIEVE_BUFFER}, {Block:Read(Size)})`)
		end,
		Write = function(Value: string, Block: Block)
			Block:Line(`buffer.write{Type}({SEND_BUFFER}, {Block:Allocate(Size)}, {Value})`)
		end,
	}

	local Primitive = {
		Type = "number",
		Generate = GeneratePrimitivePrefab("number", Prefab, Asserts.number),
	}

	return Prefab, Primitive
end

--> Numbers
Types.u8, Primitives.u8 = GenerateNumberGenerator("u8", BYTE)
Types.u16, Primitives.u16 = GenerateNumberGenerator("u16", SHORT)
Types.u32, Primitives.u32 = GenerateNumberGenerator("u32", INTEGER)

Types.i8, Primitives.i8 = GenerateNumberGenerator("i8", BYTE)
Types.i16, Primitives.i16 = GenerateNumberGenerator("i16", SHORT)
Types.i32, Primitives.i32 = GenerateNumberGenerator("i32", INTEGER)

Types.f32, Primitives.f32 = GenerateNumberGenerator("f32", FLOAT)
Types.f64, Primitives.f64 = GenerateNumberGenerator("f64", DOUBLE)

Types.f16 = {
	Read = function(Variable: string, Block: Block)
		Types.u16.Read("Encoded", Block)
		Block:Declare(Variable, true)
		Block:Line(`local MantissaExponent = Encoded % 0x8000`)
		Block:Compare("MantissaExponent", "0b0_11111_0000000000 ", "Equals")
			:Compare("Encoded // 0x8000", "1", "Equals")
			:Line(`{Variable} = -math.huge`)
			:Branch("Default")
			:Line(`{Variable} = math.huge`)
			:End()
			:Branch("Conditional", "MantissaExponent", "0b1_11111_0000000000  ", "Equals")
			:Line(`{Variable} = 0 / 0`)
			:Branch("Conditional", "MantissaExponent", "0b0_00000_0000000000  ", "Equals")
			:Line(`{Variable} = 0`)
			:Branch("Default")
			:Lines({
				"local Mantissa = MantissaExponent % 0x400\n",
				"local Exponent = MantissaExponent // 0x400\n",
				"local Fraction;\n",
				"if Exponent == 0 then\n",
				"	Fraction = Mantissa / 0x400\n",
				"else\n",
				"	Fraction = Mantissa / 0x800 + 0.5\n",
				"end\n",
				"local Result = math.ldexp(Fraction, Exponent - 14)\n",
			}, Block.Indent + 1)
			:Line(`{Variable} = if Encoded // 0x8000 == 1 then -Result else Result`)
			:End()
	end,
	Write = function(Value: string, Block: Block)
		local Allocation = Block:Allocate(HALF_FLOAT)
		Block:Compare(Value, "65504", "Greater")
			:Line(`buffer.writeu16({SEND_BUFFER}, {Allocation}, 0b0_11111_0000000000)`)
			:Branch("Conditional", Value, "-65504", "Less")
			:Line(`buffer.writeu16({SEND_BUFFER}, {Allocation}, 0b1_11111_0000000000)`)
			:Branch("Conditional", Value, Value, "Not")
			:Line(`buffer.writeu16({SEND_BUFFER}, {Allocation}, 0b1_11111_0000000001)`)
			:Branch("Conditional", Value, "0", "Equals")
			:Line(`buffer.writeu16({SEND_BUFFER}, {Allocation}, 0)`)
			:Branch("Default")
			:Line(`local float = {Value}`)
			:Lines({
				"local Abosulte = math.abs(float)\n",
				"local Interval = math.ldexp(1, math.floor(math.log(Abosulte, 2)) - 10)\n",
				"local RoundedValue = (Abosulte // Interval) * Interval\n",
				"local Fraction, Exponent = math.frexp(RoundedValue)\n",
				"Exponent += 14\n",
				"local Mantissa = math.round(if Exponent <= 0\n",
				"	then Fraction * 0x400 / math.ldexp(1, math.abs(Exponent))\n",
				"	else Fraction * 0x800) % 0x400\n",
				"local Result = Mantissa\n",
				"	+ math.max(Exponent, 0) * 0x400\n",
				"	+ if float < 0 then 0x8000 else 0\n",
			}, Block.Indent + 1)
			:Line(`buffer.writeu16({SEND_BUFFER}, {Allocation}, Result)`)
			:End()
	end,
}

Primitives.f16 = {
	Type = "number",
	Generate = GeneratePrimitivePrefab("number", Types.f16, Asserts.number),
}

--> Boolean
do
	Types.boolean = {
		Read = function(Variable: string, Block: Block)
			Block:Line(`{Block:Declare(Variable)} = (buffer.readu8({RECIEVE_BUFFER}, {Block:Read(BYTE)}) == 1)`)
		end,
		Write = function(Value: string, Block: Block)
			Block:Line(`buffer.writeu8({SEND_BUFFER}, {Block:Allocate(BYTE)}, {Value} and 1 or 0)`)
		end,
	}

	Primitives.boolean = {
		Type = "boolean",
		Generate = GeneratePrimitivePrefab("boolean", Types.boolean),
	}
end

--> String
do
	Types.string = {
		Read = function(Variable: string, Block: Block, Range: NumberRange?)
			--> Fixed size string
			if Range and Range.Min == Range.Max then
				Block:Line(
					`{Block:Declare(Variable)} = buffer.readstring({RECIEVE_BUFFER}, {Block:Read(Range.Min)}, {Range.Min})`
				)
				return
			end

			local Upper = Range and Range.Max or (2 ^ 16 - 1)
			local LengthType = GetTypeToUse(Upper)

			LengthType.Read("Length", Block)
			Block:Line(`{Block:Declare(Variable)} = buffer.readstring({RECIEVE_BUFFER}, Read(Length), Length)`)
		end,
		Write = function(Value: string, Block: Block, Range: NumberRange?)
			if Range and Range.Min == Range.Max then
				Block:Line(`buffer.writestring({SEND_BUFFER}, {Block:Allocate(Range.Min)}, {Value}, {Range.Min})`)
				return
			end

			local Upper = Range and Range.Max or (2 ^ 16 - 1)
			local LengthType = GetTypeToUse(Upper)

			Block:Line(`local Length = #{Value}`)
			LengthType.Write("Length", Block)
			Block:Line(`Allocate(Length)`)
			Block:Line(`buffer.writestring({SEND_BUFFER}, {SEND_POSITION}, {Value}, Length)`)
		end,
	}

	Primitives.string = {
		Type = "string",
		Generate = GeneratePrimitivePrefab("string", Types.string, Asserts.string),
	}
end

--> Vector
do
	Types.vector = {
		Read = function(Variable: string, Block: Block, _, Components: { string }?)
			local Prefab: TypePrefab = Types.f32
			if Components then
				Prefab = Types[Components[#Components]] or Prefab
			end

			Prefab.Read("X", Block)
			Prefab.Read("Y", Block)
			Prefab.Read("Z", Block)
			Block:Line(`{Block:Declare(Variable)} = Vector3.new(X, Y, Z)`)
		end,
		Write = function(Value: string, Block: Block, _, Components: { string }?)
			local Prefab: TypePrefab = Types.f32
			if Components then
				Prefab = Types[Components[#Components]] or Prefab
			end

			Block:Line(`local Vector = {Value}`)
			Prefab.Write("Vector.X", Block)
			Prefab.Write("Vector.Y", Block)
			Prefab.Write("Vector.Z", Block)
		end,
	}

	Primitives.vector = {
		Type = "Vector3",
		Generate = GeneratePrimitivePrefab("Vector3", Types.vector, Asserts.vector),
	}
end

--> Buffer
do
	Types.buffer = {
		Read = function(Variable: string, Block: Block, Range: NumberRange?)
			if Range and Range.Min == Range.Max then
				local Length = Range.Max
				Block:Line(`{Block:Declare(Variable)} = buffer.create({Length})`)
				Block:Line(`buffer.copy({Variable}, 0, {RECIEVE_BUFFER}, {Block:Read(Length)}, {Length})`)
				return
			end

			local Upper = Range and Range.Max or (2 ^ 16 - 1)
			local LengthType = GetTypeToUse(Upper)
			LengthType.Read("Length", Block)
			Block:Line(`{Block:Declare(Variable)} = buffer.create(Length)`)
			Block:Line(`buffer.copy({Variable}, 0, {RECIEVE_BUFFER}, Read(Length), Length)`)
		end,
		Write = function(Value: string, Block: Block, Range: NumberRange?)
			if Range and Range.Min == Range.Max then
				Block:Line(`buffer.copy({SEND_BUFFER}, {Block:Allocate(Range.Max)}, {Value}, 0, {Range.Max})`)
				return
			end

			local Upper = Range and Range.Max or (2 ^ 16 - 1)
			local LengthType = GetTypeToUse(Upper)
			Block:Line(`local Length = buffer.len({Value})`)
			LengthType.Write("Length", Block)
			Block:Line(`Allocate(Length)`)
			Block:Line(`buffer.copy({SEND_BUFFER}, {SEND_POSITION}, {Value}, 0, Length)`)
		end,
	}

	Primitives.buffer = {
		Type = "buffer",
		Generate = GeneratePrimitivePrefab("buffer", Types.buffer, Asserts.buffer),
	}
end

--> CFrame
do
	Types.CFrame = {
		Read = function(Variable: string, Block: Block, _, Components: { string }?)
			local Prefab: TypePrefab = Types.f32
			if Components then
				Prefab = Types[Components[1]]
			end

			Types.vector.Read("Position", Block, nil, Components)
			Prefab.Read("rX", Block)
			Prefab.Read("rY", Block)
			Prefab.Read("rZ", Block)
			Block:Line(`{Block:Declare(Variable)} = CFrame.new(Position) * CFrame.fromOrientation(rX, rY, rZ)`)
		end,
		Write = function(Value: string, Block: Block, _, Components: { string }?)
			local Prefab: TypePrefab = Types.f32
			if Components then
				Prefab = Types[Components[1]]
			end

			Types.vector.Write(`{Value}.Position`, Block, nil, Components)
			Block:Line(`local rX, rY, rZ = {Value}:ToOrientation()`)
			Prefab.Write("rX", Block)
			Prefab.Write("rY", Block)
			Prefab.Write("rZ", Block)
		end,
	}

	Primitives.CFrame = {
		Type = "CFrame",
		Generate = GeneratePrimitivePrefab("CFrame", Types.CFrame),
	}
end

--> Color3
do
	Types.Color3 = {
		Read = function(Variable: string, Block: Block)
			local R = Block:Read(BYTE)
			local G = Block:Read(BYTE)
			local B = Block:Read(BYTE)
			Block:Line(
				`{Block:Declare(Variable)} = Color3.fromRGB(buffer.readu8({RECIEVE_BUFFER}, {R}), buffer.readu8({RECIEVE_BUFFER}, {G}), buffer.readu8({RECIEVE_BUFFER}, {B}))`
			)
		end,
		Write = function(Value: string, Block: Block)
			Block:Line(`local Color = {Value}`)
			Types.u8.Write("Color.R * 255", Block)
			Types.u8.Write("Color.G * 255", Block)
			Types.u8.Write("Color.B * 255", Block)
		end,
	}

	Primitives.Color3 = {
		Type = "Color3",
		Generate = GeneratePrimitivePrefab("Color3", Types.Color3),
	}
end

--> DateTime
do
	Types.DateTime = {
		Read = function(Variable: string, Block: Block)
			local UnixTimestamp = Block:Read(DOUBLE)
			Block:Line(
				`{Block:Declare(Variable)} = DateTime.fromUnixTimestamp(buffer.readf64({RECIEVE_BUFFER}, {UnixTimestamp}))`
			)
		end,
		Write = function(Value: string, Block: Block)
			Block:Line(`local DateTime = {Value}`)
			Types.f64.Write(`DateTime.UnixTimestamp`, Block)
		end,
	}

	Primitives.DateTime = {
		Type = "DateTime",
		Generate = GeneratePrimitivePrefab("DateTime", Types.DateTime),
	}
end

--> DateTimeMillis
do
	Types.DateTimeMillis = {
		Read = function(Variable: string, Block: Block)
			local UnixTimestampMillis = Block:Read(DOUBLE)
			Block:Line(
				`{Block:Declare(Variable)} = DateTime.fromUnixTimestampMillis(buffer.readf64({RECIEVE_BUFFER}, {UnixTimestampMillis}))`
			)
		end,
		Write = function(Value: string, Block: Block)
			Block:Line(`local DateTimeMillis = {Value}`)
			Types.f64.Write(`DateTimeMillis.UnixTimestampMillis`, Block)
		end,
	}

	Primitives.DateTimeMillis = {
		Type = "DateTime",
		Generate = GeneratePrimitivePrefab("DateTime", Types.DateTimeMillis),
	}
end

--> BrickColor
do
	Types.BrickColor = {
		Read = function(Variable: string, Block: Block)
			local Number = Block:Read(SHORT)
			Block:Line(`{Block:Declare(Variable)} = BrickColor.new(buffer.readu16({RECIEVE_BUFFER}, {Number}))`)
		end,
		Write = function(Value: string, Block: Block)
			Block:Line(`local BrickColor = {Value}`)
			Types.u16.Write("BrickColor.Number", Block)
		end,
	}

	Primitives.BrickColor = {
		Type = "BrickColor",
		Generate = GeneratePrimitivePrefab("BrickColor", Types.BrickColor),
	}
end

--> Instance
do
	Types.Instance = {
		Read = function(Variable: string, Block: Block)
			--Block:Line("RecieveInstanceCursor += 1")
			Block:Line(`{Block:Declare(Variable)} = RecieveInstances[RecieveInstanceCursor]`)
		end,
		Write = function(Value: string, Block: Block)
			Block:Line(`table.insert(SendInstances, {Value} or Null)`)
		end,
	}

	Primitives.Instance = {
		Type = function(Declaration)
			return Declaration.Value.Class
		end,
		Generate = GeneratePrimitivePrefab("any", Types.Instance, Asserts.Instance),
	}
end

--> unknown
do
	Types.unknown = {
		Read = function(Variable: string, Block: Block)
			Block:Line("RecieveInstanceCursor += 1")
			Block:Line(`{Block:Declare(Variable)} = RecieveInstances[RecieveInstanceCursor]`)
		end,
		Write = function(Value: string, Block: Block)
			Block:Line(`table.insert(SendInstances, if {Value} == nil then Null else {Value})`)
		end,
	}

	Primitives.unknown = {
		Type = "any",
		Generate = GeneratePrimitivePrefab("any", Types.unknown),
	}
end

return {
	Types = Types,
	Asserts = Asserts,
	Primitives = Primitives,
	Structures = Structures,
	GetTypeToUse = GetTypeToUse,
	GetTypeToUseByBits = GetTypeToUseByBits,
}


--!native
--!optimize 2

local Builder = {}

function Builder.new(): Builder
	local Strings = table.create(512)

	local function PushLines(Lines: { string }, Returns: number?, Tabs: number?)
		local Size = #Lines
		local Last = Lines[Size]

		local Tabs = Tabs or 0
		local Returns = Returns or 0

		--> FAST PATH :SUNGLASSES:
		if Tabs == 0 and Returns == 0 then
			table.move(Lines, 1, Size, #Strings + 1, Strings)
			return
		end

		local Indentation = string.rep("\t", Tabs)

		if Last == "" then
			Size -= 1
			Lines[Size] = nil
		end

		for Index, Line in Lines do
			table.insert(Strings, `{Indentation}{Line}{Index == Size and string.rep("\n", Returns) or ""}`)
		end
	end

	return {
		Push = function(Text: string, Returns: number?, Tabs: number?)
			table.insert(Strings, `{string.rep("\t", Tabs or 0)}{Text}\n{string.rep("\n", Returns or 0)}`)
		end,

		PushFront = function(Text: string, Returns: number?, Tabs: number?)
			table.insert(Strings, 1, `{string.rep("\n", Returns or 1)}{Text}{string.rep("\t", Tabs or 0)}\n`)
		end,

		PushLines = PushLines,
		PushMultiline = function(Text: string, Returns: number?, Tabs: number?)
			PushLines(string.split(Text, "\n"), Returns, Tabs)
		end,

		Print = function()
			print(table.concat(Strings))
		end,

		DumpNoClear = function()
			return table.concat(Strings)
		end,

		Dump = function(): string
			local Text = table.concat(Strings)
			table.clear(Strings)
			return Text
		end,

		DumpLines = function(): { string }
			return Strings
		end,
	}
end

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

return Builder


--!native
--!optimize 2

local IS_ROBLOX = (game ~= nil)

local stdio
local process
if not IS_ROBLOX then
	stdio = require("@lune/stdio")
	process = require("@lune/process")
end

type Slice = {
	Line: number,
	Text: string,
	Spaces: number,
	Underlines: number,
}

export type Label = {
	Span: Span,
	Text: string,
}

type Color = "red" | "green" | "blue" | "black" | "white" | "cyan" | "purple" | "yellow" | "reset"

export type Span = {
	Start: number,
	End: number,
}

local SpecialSymbols = {
	CLI = {
		Line = "─",
		Down = "│",
		DownDash = "┆",
		Cross = "┬",
		RightUp = "╭",
		LeftDown = "╯",
		RightDown = "╰",
	},
	ROBLOX = {
		Line = "-",
		Down = "|",
		DownDash = " ",
		Cross = "-",
		RightUp = "╭",
		LeftDown = "╯",
		RightDown = "╰",
	},
}

local Colors = {
	black = "rgb(0, 0, 0)",
	blue = "rgb(0, 0, 255)",
	cyan = "rgb(0, 255, 255)",
	green = "rgb(0, 255, 0)",
	purple = "rgb(163, 44, 196)",
	red = "rgb(255, 0, 0)",
	white = "rgb(255, 255, 255)",
	yellow = "rgb(255, 255, 0)",
}

local Compact = false

local Error = {
	OnEmit = nil,

	LexerUnexpectedToken = 1001,

	ParserUnexpectedEndOfFile = 2001,
	ParserUnexpectedToken = 2002,
	ParserUnknownOption = 2003,
	ParserUnexpectedSymbol = 2004,
	ParserExpectedExtraToken = 2005,

	AnalyzeReferenceInvalidType = 3001,
	AnalyzeInvalidOptionalType = 3002,
	AnalyzeNestedMap = 3003,
	AnalyzeDuplicateField = 3004,
	AnalyzeReservedIdentifier = 3005,
	AnalyzeNestedScope = 3006,
	AnalyzeUnknownReference = 3007,
	AnalyzeInvalidRangeType = 3008,
	AnalyzeInvalidRange = 3009,
	AnalyzeDuplicateDeclaration = 3010,
	AnalyzeDuplicateTypeGeneric = 3011,
	AnalyzeInvalidGenerics = 3012,
	AnalyzeInvalidExport = 3013,
	AnalyzeUnknownImport = 3014,
	AnalyzeErrorWhileImporting = 3015,
	AnalyzeOptionAfterStart = 3016,
}

Error.__index = Error

export type Class = typeof(setmetatable(
	{} :: {
		Labels: { Label },
		Source: { string },
		Message: string,

		Code: number,
		File: string,
		RawMessage: string,
		PrimarySpan: Span?,
	},
	Error
))

local Symbols = IS_ROBLOX and SpecialSymbols.ROBLOX or SpecialSymbols.CLI

local function Color(Text: string, Color: Color): string
	if IS_ROBLOX then
		return `<font color="{Colors[Color]}">{Text}</font>`
	end

	return `{stdio.color(Color)}{Text}{stdio.color("reset")}`
end

local function Style(Text: string, Style: "bold" | "dim"): string
	if IS_ROBLOX then
		return Text
	end

	return `{stdio.style(Style)}{Text}{stdio.style("reset")}`
end

function Error.SetCompact(Value: boolean)
	Compact = Value
end

function Error.GetNameFromCode(From: number): string
	for Name, Code in Error do
		if Code == From then
			return Name
		end
	end

	error(`Unknown error code: {From}`)
end

function Error.new(Code: number, Source: string, Message: string, File: string?): Class
	local Lines = string.split(Source, "\n")
	local Content = `{Color(`[E{string.format("%04i", Code)}] Error`, "red")}: {Message}`
	Content ..= `\n    {Symbols.RightUp}{Symbols.Line}[{File or "input.blink"}:1:{#Lines}]`
	Content ..= `\n    {Symbols.Down}`

	return setmetatable({
		Labels = {},
		Source = Lines,
		Message = Content,

		Code = Code,
		File = File or "input.blink",
		RawMessage = Message,
	}, Error)
end

function Error.Slice(self: Class, Span: Span): { Slice }
	local Slices = {}
	local Cursor = 1

	for Line, Text in self.Source do
		local Start = Cursor
		local Length = #Text

		--> Advance cursor
		--> Cursor + #Length = \n => \n + 1 = Next line
		Cursor += (Length + 1)

		--> Span end is past cursor
		if Span.Start > Cursor then
			continue
		end

		local Spaces = (Span.Start - Start)
		local Underlines = math.clamp(Span.End - Span.Start, 1, Length)

		table.insert(Slices, {
			Line = Line,
			Text = Text,
			Spaces = Spaces,
			Underlines = Underlines,
		})

		if Span.End <= Cursor then
			break
		end
	end

	return Slices
end

function Error.Label(self: Class, Span: Span, Text: string, TextColor: Color): Class
	local Slices = self:Slice(Span)

	--> Analyze hook
	table.insert(self.Labels, {
		Span = Span,
		Text = Text,
	})

	--> Construct message
	for Index, Slice in Slices do
		self.Message ..= `\n{Style(string.format("%03i", Slice.Line), "dim")} {Symbols.Down} {Slice.Text}`
		if Index == #Slices then
			local Length = (Slice.Underlines // 2)
			local Indent = `    {Symbols.DownDash} {string.rep(" ", Slice.Spaces)}`
			local Underlines = Color(string.rep(Symbols.Line, Length), TextColor)
			local ExtraIndent = string.rep(" ", Length)

			self.Message ..= `\n{Indent}{Underlines}{Color(Symbols.Cross, TextColor)}{Underlines}`
			self.Message ..= `\n{Indent}{ExtraIndent}{Color(Symbols.Down, TextColor)}`
			self.Message ..= `\n{Indent}{ExtraIndent}{Color(
				`{Symbols.RightDown}{Symbols.Line}{Symbols.Line}`,
				TextColor
			)} {Color(Text, TextColor)}`
		end
	end

	return self
end

function Error.Primary(self: Class, Span: Span, Text: string): Class
	self.PrimarySpan = Span
	return self:Label(Span, Text, "red")
end

function Error.Secondary(self: Class, Span: Span, Text: string): Class
	return self:Label(Span, Text, "blue")
end

function Error.Emit(self: Class): never
	self.Message ..= `\n    {Symbols.Down}`
	self.Message ..= `\n{string.rep(Symbols.Line, 4)}{Symbols.LeftDown}\n`

	if IS_ROBLOX or _G.RELEASE == nil then
		local OnEmit: ((Class) -> ())? = Error.OnEmit
		if OnEmit then
			OnEmit(self)
		end

		error(self.Message, 2)
	elseif stdio and process then
		if Compact then
			local LineStart = 0
			local LineFinish = 0

			if self.PrimarySpan then
				local Slices = self:Slice(self.PrimarySpan)
				LineStart = Slices[1].Line
				LineFinish = Slices[#Slices].Line
			end

			stdio.ewrite(
				string.format(
					"[E%04i] [L%03i:L%03i] [%s] Error: %s",
					self.Code,
					LineStart,
					LineFinish,
					self.File,
					self.RawMessage
				)
			)
		else
			stdio.ewrite(self.Message)
		end

		if _G.WATCH_THREAD then
			error(self.RawMessage)
		else
			process.exit(1)
		end
	end

	error("never")
end

return Error


--!strict

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

local function Style(text: any, style: stdio.Style): string
	return `{stdio.style(style)}{text}{stdio.style("reset")}`
end

local function Color(text: any, color: stdio.Color): string
	return `{stdio.color(color)}{text}{stdio.color("reset")}`
end

local KEYWORDS = {
	["and"] = true,
	["break"] = true,
	["do"] = true,
	["else"] = true,
	["elseif"] = true,
	["end"] = true,
	["false"] = true,
	["for"] = true,
	["function"] = true,
	["if"] = true,
	["in"] = true,
	["local"] = true,
	["nil"] = true,
	["not"] = true,
	["or"] = true,
	["repeat"] = true,
	["return"] = true,
	["then"] = true,
	["true"] = true,
	["until"] = true,
	["while"] = true,
}

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

	if #value == 0 then
		return false
	end

	if string.find(value, "[^%d%a_]") then
		return false
	end

	if tonumber(string.sub(value, 1, 1)) then
		return false
	end

	if KEYWORDS[value] then
		return false
	end

	return true
end

local Cache
local INDENT = string.rep(" ", 3)

local function Noop() end

local function Format(Value: any, Filter: ((Value: any) -> boolean)?, Depth: number?): string
	local Depth = Depth or 0
	local Filter = (Filter or Noop) :: (Value: any) -> boolean

	if Depth == 0 then
		Cache = {}
	end

	if Filter(Value) == false then
		return ""
	end

	local Type = type(Value)
	if Type == "string" then
		return Color(`"{Value}"`, "green")
	elseif Type == "number" then
		if Value == math.huge then
			return Color("math.huge", "cyan")
		end
		if Value == -math.huge then
			return Color("-math.huge", "cyan")
		end
		return Color(Value, "cyan")
	elseif Type == "boolean" then
		return Color(Value, "yellow")
	elseif Type == "table" then
		local Address = string.match(tostring(Value), "0x[%w]+")
		if Cache[Value] then
			return `{Color("{CACHED}", "red")} {Style(Address, "dim")}`
		end

		local Tabs = string.rep(INDENT, Depth)
		local Newline = ("\n" .. INDENT .. Tabs)
		local Text = Style(`\{ ({Address})`, "dim") .. Newline

		Cache[Value] = true

		local First = true
		for Key, Value in Value do
			if Filter(Key) == false then
				continue
			end

			local KeyText = ShouldntWrap(Key) and Key or `["{Format(Key, Filter, Depth + 1)}"]`

			if not First then
				Text ..= Style(",", "dim") .. Newline
			end

			First = false
			Text ..= `{KeyText} = {Format(Value, Filter, Depth + 1)}`
		end

		Text ..= "\n" .. Tabs .. Style("}", "dim")
		return Text
	end

	return `<{Type}>`
end

return Format


--!strict

---- Imports ----

---- Settings ----

---- Constants ----

local Utility = {}

---- Variables ----

---- Private Functions ----

---- Public Functions ----

function Utility.PathType(Path: string): "absolute" | "relative"
	-- on unix-based systems, '/' is root
	if string.sub(Path, 1, 1) == "/" then
		return "absolute"
	end

	-- on windows systems, it is %w:[\\/]
	if string.sub(Path, 2, 2) == ":" then
		return "absolute"
	end

	return "relative"
end

function Utility.Components(Path: string): (string?, string?, string?)
	local Filename: string?
	local Directory, FilenameExt, Extension = string.match(Path, "(.-)([^\\/]-%.?([^%.\\/]*))$")

	if FilenameExt then
		if FilenameExt == Extension then
			Extension = nil
			Filename = FilenameExt
		elseif Extension then
			Filename = string.sub(FilenameExt, 1, #FilenameExt - (#Extension + 1))
		end
	end

	return Directory, Filename, Extension
end

function Utility.Filename(Path: string): string?
	local _, Filename = Utility.Components(Path)
	return Filename
end

function Utility.Directory(Path: string): string?
	local Directory = Utility.Components(Path)
	return Directory
end

function Utility.Extension(Path: string): string?
	local _, _, Extension = Utility.Components(Path)
	return Extension
end

function Utility.NameWithExtension(Path: string): string?
	local _, Name, Extension = Utility.Components(Path)
	if Name and Extension then
		return `{Name}.{Extension}`
	end

	return
end

---- Initialization ----

---- Connections ----

return Utility


return {
	Merge = function(...: { any }): { any }
		local Tables = { ... }
		local Allocate = 0

		for _, Table in Tables do
			Allocate += #Table
		end

		local Index = 1
		local Merged = table.create(Allocate)

		for _, Table in Tables do
			table.move(Table, 1, #Table, Index, Merged)
			Index += #Table
		end

		return Merged
	end,
	DeepClone = function(Table: { [any]: any }): { [any]: any }
		local Cache = {}

		local function Clone(Original)
			local Cached = Cache[Original]
			if Cached ~= nil then
				return Cached
			end

			local Copy = Original
			if type(Original) == "table" then
				Copy = {}
				Cache[Original] = Copy

				for Key, Value in Original do
					Copy[Clone(Key)] = Clone(Value)
				end
			end

			return Copy
		end

		return Clone(Table)
	end,
}


type NumberRange = { Min: number, Max: number }
local NumberRange = {
	new = function(Min: number, Max: number?): NumberRange
		return {
			Min = Min,
			Max = Max or Min,
		}
	end,
}

export type Case = "Camel" | "Snake" | "Pascal"
export type Cases = {
	Fire: string,
	FireAll: string,
	FireList: string,
	FireExcept: string,
	On: string,
	Invoke: string,
	StepReplication: string,
	Next: string,
	Iter: string,
	Read: string,
	Write: string,
}

export type Primitive = {
	Bounds: NumberRange?,
	Integer: boolean?,
	Component: boolean,
	AllowsRange: boolean,
	AllowsOptional: boolean,
	AllowedComponents: number,
}

local Indexes = {
	Pascal = 1,
	Camel = 2,
	Snake = 3,
}

local Cases = {
	On = { "On", "on", "on" },
	Invoke = { "Invoke", "invoke", "invoke" },
	Fire = { "Fire", "fire", "fire" },
	FireAll = { "FireAll", "fireAll", "fire_all" },
	FireList = { "FireList", "fireList", "fire_list" },
	FireExcept = { "FireExcept", "fireExcept", "fire_except" },
	StepReplication = { "StepReplication", "stepReplication", "step_replication" },
	Next = { "Next", "next", "next" },
	Iter = { "Iter", "iter", "iter" },
	Read = { "Read", "read", "read" },
	Write = { "Write", "write", "write" },
}

local Primitives: { [string]: Primitive } = {
	u8 = {
		Bounds = NumberRange.new(0, 255),
		Integer = true,
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	u16 = {
		Bounds = NumberRange.new(0, 65535),
		Integer = true,
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	u32 = {
		Bounds = NumberRange.new(0, 4294967295),
		Integer = true,
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	i8 = {
		Bounds = NumberRange.new(-128, 127),
		Integer = true,
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	i16 = {
		Bounds = NumberRange.new(-32768, 32767),
		Integer = true,
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	i32 = {
		Bounds = NumberRange.new(-2147483648, 2147483647),
		Integer = true,
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	f16 = {
		Bounds = NumberRange.new(-65504, 65504),
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	f32 = {
		Bounds = NumberRange.new(-16777216, 16777216),
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	f64 = {
		Bounds = NumberRange.new(-2 ^ 53, 2 ^ 53),
		Component = true,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	boolean = {
		Component = false,
		AllowsRange = false,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	string = {
		Bounds = NumberRange.new(0, 4294967295),
		Integer = true,
		Component = false,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	vector = {
		Component = false,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 1,
	},
	buffer = {
		Bounds = NumberRange.new(0, 4294967295),
		Integer = true,
		Component = false,
		AllowsRange = true,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	Color3 = {
		Component = false,
		AllowsRange = false,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	CFrame = {
		Component = false,
		AllowsRange = false,
		AllowsOptional = true,
		AllowedComponents = 2,
	},
	Instance = {
		Component = false,
		AllowsRange = false,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	BrickColor = {
		Component = false,
		AllowsRange = false,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	unknown = {
		Component = false,
		AllowsRange = false,
		AllowsOptional = false,
		AllowedComponents = 0,
	},
	DateTime = {
		Component = false,
		AllowsRange = false,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
	DateTimeMillis = {
		Component = false,
		AllowsRange = false,
		AllowsOptional = true,
		AllowedComponents = 0,
	},
}

return {
	Keywords = {
		map = true,
		set = true,
		type = true,
		enum = true,
		struct = true,

		event = true,
		["function"] = true,

		scope = true,
		option = true,

		export = true,
	},

	Primtives = Primitives,
	GetCasing = function(Case: Case): Cases
		local Index = Indexes[Case]
		if not Index then
			error(`Unknown casing "{Case}", expectd one of "Pascal" or "Camel" or "Snake"`)
		end

		local Casing: any = {}
		for Key, Options in Cases do
			Casing[Key] = Options[Index]
		end
		return Casing
	end,
}


return [[local Invocations = 0

local SendSize = 64
local SendOffset = 0
local SendCursor = 0
local SendBuffer = buffer.create(64)
local SendInstances = {}

local RecieveCursor = 0
local RecieveBuffer = buffer.create(64)

local RecieveInstances = {}
local RecieveInstanceCursor = 0

local Null = newproxy()

type Entry = {
    value: any,
    next: Entry?
}

type Queue = {
    head: Entry?,
    tail: Entry?
}

type BufferSave = {
    Size: number,
    Cursor: number,
    Buffer: buffer,
    Instances: {Instance}
}

local function Read(Bytes: number)
    local Offset = RecieveCursor
    RecieveCursor += Bytes
    return Offset
end

local function Save(): BufferSave
    return {
        Size = SendSize,
        Cursor = SendCursor,
        Buffer = SendBuffer,
        Instances = SendInstances
    }
end

local function Load(Save: BufferSave?)
    if Save then
        SendSize = Save.Size
        SendCursor = Save.Cursor
        SendOffset = Save.Cursor
        SendBuffer = Save.Buffer
        SendInstances = Save.Instances
        return
    end

    SendSize = 64
    SendCursor = 0
    SendOffset = 0
    SendBuffer = buffer.create(64)
    SendInstances = {}
end

local function Invoke()
    if Invocations == 255 then
        Invocations = 0
    end

    local Invocation = Invocations
    Invocations += 1
    return Invocation
end

local function Allocate(Bytes: number)
    local InUse = (SendCursor + Bytes)
    if InUse > SendSize then
        --> Avoid resizing the buffer for every write
        while InUse > SendSize do
            SendSize *= 1.5
        end

        local Buffer = buffer.create(SendSize)
        buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
        SendBuffer = Buffer
    end

    SendOffset = SendCursor
    SendCursor += Bytes

    return SendOffset
end

local function CreateQueue(): Queue
    return {
        head = nil,
        tail = nil
    }
end

local function Pop(queue: Queue): any
    local head = queue.head
    if head == nil then
        return
    end

    queue.head = head.next
    return head.value
end

local function Push(queue: Queue, value: any)
    local entry: Entry = {
        value = value,
        next = nil
    }

    if queue.tail ~= nil then
        queue.tail.next = entry
    end

    queue.tail = entry

    if queue.head == nil then
        queue.head = entry
    end
end

local Calls = table.create(256)

local Events: any = {
    Reliable = table.create(256),
    Unreliable = table.create(256)
}

local Queue: any = {
    Reliable = table.create(256),
    Unreliable = table.create(256)
}

]]


return [[local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

-- SPLIT --
if not RunService:IsClient() then
    error("Client network module can only be required from the client.")
end

local Reliable: RemoteEvent = ReplicatedStorage:WaitForChild(BASE_EVENT_NAME .. "_RELIABLE_REMOTE") :: RemoteEvent
local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:WaitForChild(BASE_EVENT_NAME .. "_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent

local function StepReplication()
    if SendCursor <= 0 then
        return
    end

    local Buffer = buffer.create(SendCursor)
    buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
    Reliable:FireServer(Buffer, SendInstances)

    SendSize = 64
    SendCursor = 0
    SendOffset = 0
    SendBuffer = buffer.create(64)
    table.clear(SendInstances)
end
]]


return [[local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

-- SPLIT --
if not RunService:IsServer() then
    error("Server network module can only be required from the server.")
end

local Reliable: RemoteEvent = ReplicatedStorage:FindFirstChild(BASE_EVENT_NAME .. "_RELIABLE_REMOTE") :: RemoteEvent
if not Reliable then
    local RemoteEvent = Instance.new("RemoteEvent")
    RemoteEvent.Name = BASE_EVENT_NAME .. "_RELIABLE_REMOTE"
    RemoteEvent.Parent = ReplicatedStorage
    Reliable = RemoteEvent
end

local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:FindFirstChild(BASE_EVENT_NAME .. "_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
if not Unreliable then
    local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")
    UnreliableRemoteEvent.Name = BASE_EVENT_NAME .. "_UNRELIABLE_REMOTE"
    UnreliableRemoteEvent.Parent = ReplicatedStorage
    Unreliable = UnreliableRemoteEvent
end

local PlayersMap: {[Player]: BufferSave} = {}

Players.PlayerRemoving:Connect(function(Player)
    PlayersMap[Player] = nil
end)

local function StepReplication()
    for Player, Send in PlayersMap do
        if Send.Cursor <= 0 then
            continue
        end

        local Buffer = buffer.create(Send.Cursor)
        buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor)
        Reliable:FireClient(Player, Buffer, Send.Instances)

        Send.Size = 64
        Send.Cursor = 0
        Send.Buffer = buffer.create(64)
        table.clear(Send.Instances)
    end
end
]]


return function(Vargs)
	local client, service = Vargs.Client, Vargs.Service

	--Acts the same as a server plugin but with client functions instead of server.
	--[[
	local window = client.UI.Make("Window",{
		Title = "Changing DataStore";
		Size = {700,300};
		Icon = "rbxassetid://357249130";
	})

	window:Add("ImageLabel",{
		Image = "rbxassetid://531490964";
	})

	--]]
end


--[[
	SERVER PLUGINS' NAMES MUST START WITH "Server:" OR "Server-"
	CLIENT PLUGINS' NAMES MUST START WITH "Client:" OR "Client-"

	Plugins have full access to the server/client tables and most variables.

	You can use the MakePluginEvent to use the script instead of setting up an event.
	PlayerJoined will fire after the player finishes initial loading
	CharacterAdded will also fire after the player is loaded, it does not use the CharacterAdded event.

	service.Events.PlayerAdded:Connect(function(p)
		print(`{p.Name} Joined! Example Plugin`)
	end)

	service.Events.CharacterAdded:Connect(function(p)
		server.RunCommand('name', plr.Name, 'BobTest Example Plugin')
	end)

--]]

return function(Vargs)
	local server, service = Vargs.Server, Vargs.Service

	server.Commands.ExampleCommand = {
		Prefix = server.Settings.Prefix;	-- Prefix to use for command
		Commands = {"example"};	-- Commands
		Args = {"arg1"};	-- Command arguments
		Description = "Example command";	-- Command Description
		Hidden = true; -- Is it hidden from the command list?
		Fun = false;	-- Is it fun?
		AdminLevel = "Players";	    -- Admin level; If using settings.CustomRanks set this to the custom rank name (eg. "Baristas")
		Function = function(plr,args)    -- Function to run for command
			print("HELLO WORLD FROM AN EXAMPLE COMMAND :)")
			print(`Player supplied args[1] {args[1]}`)
		end
	}
end


--[[
	Use the below table to define "pre-set" cameras

	Format example:

		Cameras = {
			{
				Name = "CameraName";
				Position = Vector3; -- Example: Vector3.new(0, 0, 0) or workspace.ExampleCameraPart.Position
			}
		}
--]]

----------------------
-- CAMERAS SETTINGS --
----------------------

return {
	Cameras = {
		-- {
		-- 	Name = "ExampleCamera1;
		-- 	Position = Vector3.new(0, 50, 0)
		-- }
	}
}


-------------------------------
-- Scroll down for settings  --
-------------------------------

--[[
	Format example for Aliases:

		Aliases = {
			[":alias <arg1> <arg2> ..."] = ":command <arg1> <arg2> ..."
		}

	Format example for CommandCooldowns:

		CommandCooldowns = {
			[":commandname"] = {
				Player = 0; -- (optional) Per player cooldown in seconds
				Server = 0; -- (optional) Per server cooldown in seconds
				Cross = 0; -- (optional) Global cooldown in seconds
			}
		}

		Make sure to include the prefix infront of the command name.

	Format example for Permissions:

		Permissions = {
			"CommandName:NewLevel";
			"CommandName:CustomRank1,CustomRank2,CustomRank3";
		};
--]]

----------------------------------
-- COMMAND ADJUSTMENTS SETTINGS --
----------------------------------

return {
	Aliases = {
		-- [":examplealias <player> <fireColor>"] = ":ff <player> | :fling <player> | :fire <player> <fireColor>" -- Order arguments appear in alias string determines their required order in the command message when ran later
	};

	CommandCooldowns = {
		-- [":logs"] = {
		-- 	Player = 5; -- 5 seconds
		-- }
	};

	Permissions = {
		-- "ff:HeadAdmins";	-- Changes :ff to HeadAdmins and higher (HeadAdmins = Level 300 by default)
		-- "kill:300";		-- Changes :kill to level 300 and higher (Level 300 = HeadAdmins by default)
		-- "ban:200,300";	-- Makes it so :ban is only usable by levels 200 and 300 specifically (nothing higher or lower or in between)
	};
}


--------------------
-- DONOR SETTINGS --
--------------------

return {
	DonorCapes = true;		-- Donors get to show off their capes; Not disruptive :)
	DonorCommands = true;	-- Show your support for the script and let donors use harmless commands like !sparkles
	LocalCapes = false;		-- Makes Donor capes local so only the donors see their cape [All players can still disable capes locally]
};


----------------------
-- GENERAL SETTINGS --
----------------------

return {
	Prefix = {":", ";"};		-- A list of prefixes for commands, the : in :kill me
	PlayerPrefix = "!";			-- The ! in !donate; Mainly used for commands that any player can run; Do not make it the same as Prefix
	SpecialPrefix = "";			-- Used for things like "all", "me" and "others" (If changed to ! you would do :kill !me)
	SplitKey = " ";				-- The space in :kill me (eg if you change it to / :kill me would be :kill/me)
	BatchKey = "|";				-- :kill me | :ff bob | :explode scel
	ConsoleKeyCode = "Quote";	-- Keybind to open the console; Rebindable per player in userpanel; KeyCodes: https://developer.roblox.com/en-us/api-reference/enum/KeyCode

	HideScript = true;	-- When the game starts the Adonis_Loader model will be hidden so other scripts cannot access the settings module; Disable if your game uses AssetService:SavePlaceAsync()

	DataStoreEnabled = true;		-- Disable if you don't want to load settings and admins from the datastore; PlayerData will still save
	LocalDatastore = false;			-- If this is turned on, a mock DataStore will forcibly be used instead and shall never save across servers
	DataStore = "Adonis_1";			-- DataStore the script will use for saving data; Changing this will lose any saved data
	DataStoreKey = "CHANGE_THIS";	-- CHANGE THIS TO ANYTHING RANDOM! Key used to encrypt all datastore entries; Changing this will lose any saved data

	SaveAdmins = true;			-- If true anyone you :admin or :headadmin in-game will save
	LoadAdminsFromDS = true;	-- If false, any admins saved in your DataStores will not load

	SaveCommandLogs = true;	-- If command logs are saved to the datastores
	MaxLogs = 5000;			-- Maximum logs to save before deleting the oldest

	Storage = game:GetService("ServerStorage");	-- Where things like tools are stored
	RecursiveTools = false;	-- Whether tools that are included in sub-containers within Storage will be available via the :give command (useful if your tools are organized into multiple folders)

	Theme = "Default";			-- UI theme
	MobileTheme = "Mobilius";	-- Theme to use on mobile devices; Some UI elements are disabled
	DefaultTheme = "Default";	-- Theme to be used as a replacement for "Default". The new replacement theme can still use "Default" as its Base_Theme however any other theme that references "Default" as its redirects to this theme.
	HiddenThemes = {};			-- Hide themes from the theme selector tab inside the userpanel. Each theme name must be the specific name such as "Mobilius"

	BanMessage = "Banned";				-- Message shown to banned users upon kick
	LockMessage = "Not Whitelisted";	-- Message shown to people when they are kicked while :slock is on in the server
	SystemTitle = "System Message";		-- Title to display in :sm and :bc

	--[[
		Format example for Banned, Muted, Blacklist and Whitelist:

		Banned, Muted, Blacklist or Whitelist = {
			"Username"; -- Example: "roblox"
			"Username:UserId"; -- Example: "roblox:1"
			UserId; -- Example: 1
			"Group:GroupId:GroupRank"; -- Example: "Group:123456:50"
			"Group:GroupId"; -- Example: "Group:123456"
			"Item:ItemID"; -- Example: "Item:123456"
			"GamePass:GamePassID"; -- Example: "GamePass:123456"
			"Subscription:SubscriptionId"; -- Example: "Subscription:123456"
		}
	--]]

	Banned = {};		-- List of people banned from the game		Format: See the above format example
	Muted = {};			-- List of people muted (cannot send chat messages)		Format: See the above format example
	Blacklist = {};		-- List of people banned from running commands		Format: See the above format example
	Whitelist = {};		-- People who can join if whitelist enabled		Format: See the above format example

	WhitelistEnabled = false; -- If true enables the whitelist/server lock; Only lets admins & whitelisted users join

	MusicList = {}; 	-- List of songs to appear in the :musiclist		Format: {{Name = "somesong", ID = 1234567}, {Name = "anotherone", ID = 1243562}}
	CapeList = {};		-- List of capes		Format: {{Name = "somecape", Material = "Fabric", Color = "Bright yellow", ID = 12345567, Reflectance = 1}; {etc more stuff here}}
	InsertList = {}; 	-- List of models to appear in the :insertlist and can be inserted using ':insert <name>'		Format: {{Name = "somemodel", ID = 1234567}; {Name = "anotherone", ID = 1243562}}

	OnStartup = {};		-- List of commands ran at server start		Format: {":notif TestNotif"}
	OnJoin = {};		-- List of commands ran as player on join (ignores adminlevel)		Format: {":cmds"}
	OnSpawn = {};		-- List of commands ran as player on spawn (ignores adminlevel)		Format: {"!fire Really red",":ff me"}

	FunCommands = true;					-- Are fun commands enabled?
	PlayerCommands = true;				-- Are player-level utility commands enabled?
	AgeRestrictedCommands = true;		-- Are age restricted commands enabled?
	ChatCommands = true;				-- If false you will not be able to run commands via the chat; Instead, you MUST use the console or you will be unable to run commands
	CrossServerCommands = true;			-- Are commands which affect more than one server enabled?
	WarnDangerousCommand = false;		-- Do dangerous commands ask for confirmation?
	CommandFeedback = false;			-- Should players be notified when commands with non-obvious effects are run on them?
	SilentCommandDenials = false;		-- If true, there will be no differences between the error messages shown when a user enters an invalid command and when they have insufficient permissions for the command
	OverrideChatCallbacks = true;		-- If the TextChatService ShouldDeliverCallbacks of all channels are overridden by Adonis on load. Required for slowmode. Mutes use a CanSend method to mute when this is set to false.
	ChatCreateRobloxCommands = false;	-- Whether "/" commands for Roblox should get created in new Chat

	CodeExecution = false;	-- Enables the use of code execution in Adonis. Scripting related (such as :s) and a few other commands require this

	Notification = true;	-- Whether or not to show the "You're an admin" and "Updated" notifications
	SongHint = true;		-- Display a hint with the current song name and ID when a song is played via :music
	TopBarShift = false;	-- By default hints and notifications will appear from the top edge of the window. Set this to true if you don't want hints/notifications to appear in that region.

	AutoClean = false;		-- Will auto clean workspace of things like hats and tools
	AutoCleanDelay = 60;	-- Time between auto cleans
	AutoBackup = false;		-- Run :backupmap automatically when the server starts. To restore the map, run :restoremap
	ReJail = false;			-- If true then when a player rejoins they'll go back into jail. Or if the moderator leaves everybody gets unjailed

	Console = true;				-- Whether the command console is enabled
	Console_AdminsOnly = false; -- If true, only admins will be able to access the console

	HelpSystem = true;	-- Allows players to call admins for help using !help
	HelpButton = true;	-- Shows a little help button in the bottom right corner.
	HelpButtonImage = "rbxassetid://357249130";	-- Sets the image used for the Adonis help button above.

	HttpWait = 60;	-- How long things that use the HttpService will wait before updating again
};


--[[
	Format example for Join Messages:

		Messages = {
			"This is a message using the defaults!";
			{
				Title = "Message title";
				Message = "Message body";
				Time = 10;
				Icon = "rbxassetid://7510999669";
				Level = 300;
			};
		}

		Messages are shown to HeadAdmins+ (300) by default but tables can define a different minimum level via .Level
--]]

return {
	Messages = {
		-- {
		-- 	Title = "Message title";
		-- 	Message = "Message body";
		-- 	Time = 10;
		-- 	Icon = "rbxassetid://7510999669";
		-- 	Level = 300;
		-- };
	};	-- A list of notifications shown on join. Messages can either be strings or tables.
};


--[[
	List of waypoints you can teleport via ':to wp-WAYPOINTNAME' or ':teleport PLAYER tp.WAYPOINTNAME'

	Use the below table to define "pre-set" waypoints

	Format example:

		Waypoints = {
			EXAMPLEWAYPOINT1NAME = Vector3; -- Example: Vector3.new(0, 0, 0) or workspace.ExampleWaypointPart.Position
		}
--]]

------------------------
-- WAYPOINTS SETTINGS --
------------------------

return {
	Waypoints = {
		-- YOURNAME1 = Vector3.new(5, 20, 10)
	};
};


--[[
	--------------------------------------------
	--                 Adonis                 --
	--          By Epix Incorporated          --
	--------------------------------------------
	--  Not Everything is so Black and White  --
	--------------------------------------------

	A lot of time goes into maintaining and updating Adonis. If you enjoy using Adonis,
	please consider leaving us a review, as your feedback means a lot:
	https://create.roblox.com/store/asset/7510622625/

	---------------------
	-- Getting started --
	---------------------

	- It's recommend you store Adonis_Loader in 'ServerScriptService'.
	- Configure Ranks in Config -> Settings -> Ranks module.
	- Configure general settings like prefixes, command settings, etc in Config -> Settings -> General module.
	- You can find the rest of the settings modules at Config -> Settings. They are sorted by categories.
	- You can create custom commands and plugins in Config -> Plugins.

	---------------------
	-- Basic luau info --
	---------------------

	This is only here to help you when editing settings so you understand how they work
	and don't break something.

	In case you don't know what Luau is; Luau is the scripting language Roblox uses...
	so every script you see (such as this one) and pretty much any code on Roblox is
	written in Luau.

	Anything that looks like {} is known as a table.
	Tables contain things, like the Luau version of a box.
	An example of a table would be setting = {"John", "Mary", "Bill"}
	You can have tables inside of tables, such in the case of setting = {{Group = 1234, Rank = 123, Type = "Admin"}}
	Just like real boxes, tables can contain pretty much anything including other tables.

	Note: Commas (,) as well as semicolons (;) can both be used to separate things inside a table.

	Anything that looks like "Bob" is what's known as a string. Strings
	are basically plain text; setting = "Bob" would be correct however
	setting = Bob would not; because if it's not surrounded by quotes Luau will think
	that Bob is a variable; Quotes indicate something is a string and therefore not a variable/number/code

	Numbers do not use quotes. setting = 56

	This block of text you are reading is called a comment. It's like a message
	from the programmer to anyone who reads their stuff. Anything in a comment will
	not be seen by Luau when the script is run.

	Comments can be made by putting -- at the start of a line

	For more information about roblox luau visit: https://create.roblox.com/docs/luau
--]]


--// NOTE: THIS IS NOT A *CONFIG/USER* PLUGIN! ANYTHING IN THE MAINMODULE PLUGIN FOLDERS IS ALREADY PART OF/LOADED BY THE SCRIPT! DO NOT ADD THEM TO YOUR CONFIG>PLUGINS FOLDER!

return function(Vargs, GetEnv)
	local client = Vargs.Client;
	local service = Vargs.Service;

	local Settings = client.Settings
	local Functions, Anti, Core, Logs, Remote, Process, Variables = client.Functions, client.Anti, client.Core, client.Logs, client.Remote, client.Process, client.Variables

	-- // Backwards compatibility
	local Pcall = client.Pcall
	local function cPcall(func, ...)
		return Pcall(function(...)
			return coroutine.resume(coroutine.create(func), ...)
		end, ...)
	end
	client.cPcall, service.cPcall = cPcall, cPcall
	service.CloneTable = function(tbl)
		return (getmetatable(tbl) and not pcall(setmetatable(tbl, getmetatable(tbl)))) and setmetatable({}, {__index = function(_, k) return tbl[k] end}) or table.clone(tbl)
	end
	service.AltUnpack = function(args, shift) -- TODO: Remove. This is not used in Adonis at all and is easily replicatable so you can safely remove it withour fear
		return table.unpack(args, 1 + (shift and shift - 1 or 0), 10 + (shift and shift - 1 or 0))
	end
	service.GoodSignal = service.Signal
	service.Yield = function()
		local event = service.Signal.new()
		return {
			Release = function(...) event:Fire(...) end;
			Wait = function(...) return event:Wait(...) end;
			Destroy = function() event:Destroy() end;
			Event = event;
		}
	end
	Remote.UnEncrypted = setmetatable({}, {
		__newindex = function(_, ind, val)
			warn("Unencrypted remote commands are deprecated; moving", ind, "to Remote.Commands. Replace `Remote.Unencrypted` with `Remote.Commands`!")
			Remote.Commands[ind] = val
			Logs:AddLog("Script", `Attempted to add {ind} to legacy Remote.Unencrypted. Moving to Remote.Commands`)
		end
	})
	client.Folder:SetSpecial("Shared", client.Shared)
	Functions.GetRandom = function(pLen)
		local random = math.random
		local format = string.format

		local Len = (type(pLen) == "number" and pLen) or random(5,10) --// reru
		local Res = {}
		for Idx = 1, Len do
			Res[Idx] = format("%02x", random(255))
		end
		return table.concat(Res)
	end
	-- TODO: Remove \\/
	service.OwnsAsset = service.CheckAssetOwnership
	Functions.CatchError = client.Pcall

	Logs:AddLog("Script", "Misc Features Module Loaded")
end


--!native
--------------------------------------------------------------------------------------------
-------------------------------------- Slider Module ---------------------------------------
-- [Adonis Maintainer]: P3tray
-- [Author]: Krypt
-- [Description]: Creates a slider based on a start, end and incremental value. Allows ...
-- ... sliders to be moved, tracked/untracked, reset, and have specific properties such ...
-- ... as their current value and increment to be overriden.

-- [Version]: 2.1.1
-- [Created]: 22/12/2021
-- [Updated]: 23/11/2022
-- [Dev Forum Link]: https://devforum.roblox.com/t/1597785/
--------------------------------------------------------------------------------------------

--!nonstrict
local Slider = {Sliders = {}}

local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")

assert(RunService:IsClient(), "Slider module can only be used on the Client!")

local SliderFuncs = {}
do
	function SliderFuncs.snapToScale(val: number, step: number): number
		return math.clamp(math.round(val / step) * step, 0, 1)
	end

	function lerp(start: number, finish: number, percent: number): number
		return (1 - percent) * start + percent * finish
	end

	function SliderFuncs.map(value: number, start: number, stop: number, newStart: number, newEnd: number, constrain: boolean): number
		local newVal = lerp(newStart, newEnd, SliderFuncs.getAlphaBetween(start, stop, value))
		if not constrain then
			return newVal
		end

		if newStart < newEnd then
			newStart, newEnd = newEnd, newStart
		end

		return math.max(math.min(newVal, newStart), newEnd)
	end

	function SliderFuncs.getNewPosition(self): UDim2
		local absoluteSize = self._data.Button.AbsoluteSize[self._config.Axis]
		local holderSize = self._holder.AbsoluteSize[self._config.Axis]

		local anchorPoint = self._data.Button.AnchorPoint[self._config.Axis]

		local paddingScale = (self._config.Padding / holderSize)

		local minScale = ((anchorPoint * absoluteSize) / holderSize + paddingScale)
		local decrement = ((2 * absoluteSize) * anchorPoint) - absoluteSize
		local maxScale = (1 - minScale) + (decrement / holderSize)

		local newPercent = SliderFuncs.map(self._data._percent, 0, 1, minScale, maxScale, true)

		return
			if self._config.Axis == "X" then UDim2.fromScale(newPercent, self._data.Button.Position.Y.Scale)
			else UDim2.fromScale(self._data.Button.Position.X.Scale, newPercent)
	end

	function SliderFuncs.getScaleIncrement(self)
		return 1 / ((self._config.SliderData.End - self._config.SliderData.Start) / self._config.SliderData.Increment)
	end

	function SliderFuncs.getAlphaBetween(a: number, b: number, c: number): number
		return (c - a) / (b - a)
	end

	function SliderFuncs.getNewValue(self)
		local newValue = lerp(self._config.SliderData.Start, self._config.SliderData.End, self._data._percent)
		local incrementScale = (1 / self._config.SliderData.Increment)

		newValue = math.round(newValue * incrementScale) / incrementScale
		return newValue
	end
end

local Signal = require(script.Parent.Signal)

Slider.__index = function(object, indexed)
	local deprecated = {
		{".OnChange", ".Changed", rawget(object, "Changed")}
	}

	for _, tbl in ipairs(deprecated) do
		local deprecatedStr = string.sub(tbl[1], 2)

		if deprecatedStr == indexed then
			warn(string.format("%s is deprecated, please use %s instead", tbl[1], tbl[2]))
			return tbl[3]
		end
	end

	return Slider[indexed]
end

export type configDictionary = {
	SliderData: {Start: number, End: number, Increment: number, DefaultValue: number | nil},
	MoveType: "Tween" | "Instant" | nil,
	MoveInfo: TweenInfo | nil,
	Axis: string | nil,
	Padding: number | nil,
	AllowBackgroundClick: boolean
}

function Slider.new(holder: GuiBase2d, config: configDictionary)
	assert(pcall(function()
		return holder.AbsoluteSize, holder.AbsolutePosition
	end), "Holder argument does not have an AbsoluteSize/AbsolutePosition")

	local duplicate = false
	for _, slider in ipairs(Slider.Sliders) do
		if slider._holder == holder then
			duplicate = true
			break
		end
	end

	assert(not duplicate, "Cannot set two sliders with same frame!")
	assert(config.SliderData.Increment ~= nil, "Failed to find Increment in SliderData table")
	assert(config.SliderData.Start ~= nil, "Failed to find Start in SliderData table")
	assert(config.SliderData.End ~= nil, "Failed to find End in SliderData table")
	assert(config.SliderData.Increment > 0, "SliderData.Increment must be greater than 0")
	assert(config.SliderData.End > config.SliderData.Start, string.format("Slider end value must be greater than its start value! (%.1f <= %.1f)", config.SliderData.End, config.SliderData.Start))

	local self = setmetatable({}, Slider)
	self._holder = holder
	self._data = {
		-- Buttons
		Button = nil,
		HolderButton = nil,

		-- Clicking
		_clickOverride = false,

		_mainConnection = nil,
		_clickConnections = {},
		_otherConnections = {},

		_inputPos = nil,

		-- Internal
		_percent = 0,
		_value = 0,
		_scaleIncrement = 0,
		_currentTween = nil,
		_allowBackgroundClick = if config.AllowBackgroundClick == false then false else true
	}

	self._config = config
	self._config.Axis = string.upper(config.Axis or "X")
	self._config.Padding = config.Padding or 5
	self._config.MoveInfo = config.MoveInfo or TweenInfo.new(0.2)
	self._config.MoveType = config.MoveType or "Tween"
	self.IsHeld = false

	local sliderBtn = holder:FindFirstChild("Slider")
	assert(sliderBtn ~= nil, "Failed to find slider button.")
	assert(sliderBtn:IsA("GuiButton"), "Slider is not a GuiButton")

	self._data.Button = sliderBtn

	-- Holder button --
	if self._data._allowBackgroundClick then
		local holderClickButton = Instance.new("TextButton")
		holderClickButton.BackgroundTransparency = 1
		holderClickButton.Text = ""
		holderClickButton.Name = "HolderClickButton"
		holderClickButton.Size = UDim2.fromScale(1, 1)
		holderClickButton.ZIndex = -1
		holderClickButton.Parent = self._holder
		self._data.HolderButton = holderClickButton
	end

	-- Finalise --

	self._data._percent = 0
	if config.SliderData.DefaultValue then
		config.SliderData.DefaultValue = math.clamp(config.SliderData.DefaultValue, config.SliderData.Start, config.SliderData.End)
		self._data._percent = SliderFuncs.getAlphaBetween(config.SliderData.Start, config.SliderData.End, config.SliderData.DefaultValue)
	end

	self._data._percent = math.clamp(self._data._percent, 0, 1)

	self._data._value = SliderFuncs.getNewValue(self)
	self._data._increment = config.SliderData.Increment
	self._data._scaleIncrement = SliderFuncs.getScaleIncrement(self)

	self.Changed = Signal.new()
	self.Dragged = Signal.new()
	self.Released = Signal.new()

	self._data._percent = SliderFuncs.snapToScale(self._data._percent, self._data._scaleIncrement)
	self:Move()

	table.insert(self._data._otherConnections, sliderBtn:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
		self:Move("Instant")
	end))

	table.insert(Slider.Sliders, self)

	return self
end

function Slider:Track()
	for _, connection in ipairs(self._data._clickConnections) do
		connection:Disconnect()
	end

	table.insert(self._data._clickConnections, self._data.Button.MouseButton1Down:Connect(function()
		self.IsHeld = true
	end))

	table.insert(self._data._clickConnections, self._data.Button.MouseButton1Up:Connect(function()
		if self.IsHeld then
			self.Released:Fire(self._data._value)
		end
		self.IsHeld = false
	end))

	if self._data._allowBackgroundClick then
		table.insert(self._data._clickConnections, self._data.HolderButton.Activated:Connect(function(inputObject: InputObject)
			if inputObject.UserInputType == Enum.UserInputType.MouseButton1 or inputObject.UserInputType == Enum.UserInputType.Touch then
				self._data._inputPos = inputObject.Position
				self._data._clickOverride = true
				self:Update()
				self._data._clickOverride = false
			end
		end))
	end

	if self.Changed then
		self.Changed:Fire(self._data._value)
	end

	if self._data._mainConnection then
		self._data._mainConnection:Disconnect()
	end

	self._data._mainConnection = UserInputService.InputChanged:Connect(function(inputObject, gameProcessed)
		if inputObject.UserInputType == Enum.UserInputType.MouseMovement or inputObject.UserInputType == Enum.UserInputType.Touch then
			self._data._inputPos = inputObject.Position
			self:Update()
		end
	end)
end

function Slider:Update()
	if (self.IsHeld or self._data._clickOverride) and self._data._inputPos then
		local sliderSize = self._holder.AbsoluteSize[self._config.Axis]
		local sliderPos = self._holder.AbsolutePosition[self._config.Axis]

		local mousePos = self._data._inputPos[self._config.Axis]

		if mousePos then
			local relativePos = (mousePos - sliderPos)
			local newPos = SliderFuncs.snapToScale(relativePos / sliderSize, self._data._scaleIncrement)

			local percent = math.clamp(newPos, 0, 1)
			self._data._percent = percent
			self.Dragged:Fire(self._data._value)
			self:Move()
		end
	end
end

function Slider:Untrack()
	for _, connection in ipairs(self._data._clickConnections) do
		connection:Disconnect()
	end
	if self._data._mainConnection then
		self._data._mainConnection:Disconnect()
	end
	self.IsHeld = false
end

function Slider:Reset()
	for _, connection in ipairs(self._data._clickConnections) do
		connection:Disconnect()
	end
	if self._data._mainConnection then
		self._data._mainConnection:Disconnect()
	end

	self.IsHeld = false

	self._data._percent = 0
	if self._config.SliderData.DefaultValue then
		self._data._percent = SliderFuncs.getAlphaBetween(self._config.SliderData.Start, self._config.SliderData.End, self._config.SliderData.DefaultValue)
	end
	self._data._percent = math.clamp(self._data._percent, 0, 1)
	self:Move()
end

function Slider:OverrideValue(newValue: number, supressEvent: boolean?) -- Patch by ccuser44 to prevent music glitching
	self.IsHeld = false
	self._data._percent = SliderFuncs.getAlphaBetween(self._config.SliderData.Start, self._config.SliderData.End, newValue)
	self._data._percent = math.clamp(self._data._percent, 0, 1)
	self._data._percent = SliderFuncs.snapToScale(self._data._percent, self._data._scaleIncrement)
	self:Move(nil, supressEvent)
end

function Slider:Move(override: string, supressEvent: boolean?)
	self._data._value = SliderFuncs.getNewValue(self)

	local moveType = if override ~= nil then override else self._config.MoveType
	if moveType == "Tween" or moveType == nil then
		if self._data._currentTween then
			self._data._currentTween:Cancel()
		end
		self._data._currentTween = TweenService:Create(self._data.Button, self._config.MoveInfo, {
			Position = SliderFuncs.getNewPosition(self)
		})
		self._data._currentTween:Play()
	elseif moveType == "Instant" then
		self._data.Button.Position = SliderFuncs.getNewPosition(self)
	end

	if not supressEvent then
		self.Changed:Fire(self._data._value)
	end
end

function Slider:OverrideIncrement(newIncrement: number)
	self._config.SliderData.Increment = newIncrement
	self._data._increment = newIncrement
	self._data._scaleIncrement = SliderFuncs.getScaleIncrement(self)
	self._data._percent = math.clamp(self._data._percent, 0, 1)
	self._data._percent = SliderFuncs.snapToScale(self._data._percent, self._data._scaleIncrement)
	self:Move()
end

function Slider:GetValue()
	return self._data._value
end

function Slider:GetIncrement()
	return self._data._increment
end

function Slider:Destroy()
	for _, connection in ipairs(self._data._clickConnections) do
		connection:Disconnect()
	end
	for _, connection in ipairs(self._data._otherConnections) do
		connection:Disconnect()
	end

	if self._data._mainConnection then
		self._data._mainConnection:Disconnect()
	end

	if self._data.HolderButton then
		self._data.HolderButton:Destroy()
		self._data.HolderButton = nil
	end

	self.Changed:Destroy()
	self.Dragged:Destroy()
	self.Released:Destroy()

	for index = 1, #Slider.Sliders do
		if Slider.Sliders[index] == self then
			table.remove(Slider.Sliders, index)
		end
	end

	setmetatable(self, nil)
	self = nil
end

UserInputService.InputEnded:Connect(function(inputObject: InputObject, internallyProcessed: boolean)
	if inputObject.UserInputType == Enum.UserInputType.MouseButton1 or inputObject.UserInputType == Enum.UserInputType.Touch then
		for _, slider in ipairs(Slider.Sliders) do
			if slider.IsHeld then
				slider.Released:Fire(slider._data._value)
			end
			slider.IsHeld = false
		end
	end
end)

return Slider
-----------------------------------------------------------------------------------------


--!native
--!optimize 2
local RunService = game:GetService("RunService")

local module = {}

local function AlphaColorSequence(Sequence, Alpha)
	local Keypoints = Sequence.Keypoints
	local StartKeypoint, EndKeypoint = 1, #Keypoints

	for i, Keypoint in ipairs(Keypoints) do
		if Keypoint.Time >= Alpha then
			EndKeypoint = Keypoint
			StartKeypoint = Keypoints[math.max(1, i-1)]
			break
		end
	end

	local StartTime, EndTime = StartKeypoint.Time, EndKeypoint.Time
	local StartValue, EndValue = StartKeypoint.Value, EndKeypoint.Value

	local KeyframeAlpha = (Alpha - StartTime) / (EndTime - StartTime)

	return StartValue:Lerp(EndValue, KeyframeAlpha)
end

function module.new(Frame, BarCount)

	-- Determine settings
	local BAR_COUNT = math.clamp(type(BarCount) == "number" and BarCount or 41, 11,601)
	local SAMPLE_HZ = math.clamp(BAR_COUNT/2,10,500)

	if BAR_COUNT%2==0 then
		BAR_COUNT += 1
	end

	local BAR_SIZE = 1/BAR_COUNT
	local BUFFER_COUNT = math.ceil(BAR_COUNT/2)
	local BAR_TO_BUFFER = table.create(BAR_COUNT)
	local BAR_TO_MULTIPLIER = table.create(BAR_COUNT)

	for i=1, BAR_COUNT do
		BAR_TO_BUFFER[i] = math.abs(BUFFER_COUNT- (i+ (i<=BUFFER_COUNT and -1 or 1) ))
		BAR_TO_MULTIPLIER[i] = 1-(math.clamp(math.abs(BUFFER_COUNT- (i+ (i<=BUFFER_COUNT and -1 or 1) ))/BUFFER_COUNT, 0.01,1)^2)
	end

	local UPDATE_WAIT = 1/SAMPLE_HZ

	-- Setup visualizer

	local Visualizer = {
		Frame = Frame;
		VolumeBuffer = table.create(BUFFER_COUNT+1);
		Bars = {};
		BufferConnection = nil;
		PlayingConnection = nil;
		Sound = nil;
	}

	-- Create the bar guis
	for i=1, BAR_COUNT do

		local Bar = Frame:Add("Frame", {
			BorderSizePixel = 0;
			BackgroundColor3 = Color3.new(0.98,0.98,0.99);
			AnchorPoint = Vector2.new(0,0.5);
			Size = UDim2.new(BAR_SIZE,0,0.02,0);
			Position = UDim2.new(BAR_SIZE* (i-1), 0,0.5,0);
		})

		Visualizer.Bars[i] = Bar
	end

	local function HandlePlaying()
		if Visualizer.BufferConnection then
			Visualizer.BufferConnection:Disconnect()
			Visualizer.BufferConnection = nil
		end

		local Sound = Visualizer.Sound
		if not Sound then return end

		if Sound.Playing then
			local LastStored = os.clock()
			Visualizer.BufferConnection = RunService.Heartbeat:Connect(function()
				local success, void = pcall(function()
					Visualizer.VolumeBuffer[1] = Sound.PlaybackLoudness
					Visualizer.Bars[BUFFER_COUNT]:TweenSize(
						UDim2.new(
							BAR_SIZE,0,
							math.clamp((Sound.PlaybackLoudness/400), 0.02,1),0
						),
						Enum.EasingDirection.Out, Enum.EasingStyle.Linear, 0.014, true
					)

					if os.clock()-LastStored > UPDATE_WAIT then
						LastStored = os.clock()

						table.insert(Visualizer.VolumeBuffer,1,Sound.PlaybackLoudness)
						local Length = #Visualizer.VolumeBuffer
						if Length > BUFFER_COUNT then
							Visualizer.VolumeBuffer[#Visualizer.VolumeBuffer] = nil
						end

						local Color = Visualizer.Color
						local ColorType = typeof(Color)

						for i,Bar in ipairs(Visualizer.Bars) do

							local Alpha = math.clamp(
								((Visualizer.VolumeBuffer[BAR_TO_BUFFER[i]] or 0)/400),
								0.02, 1
							)

							Bar:TweenSize(
								UDim2.new(
									BAR_SIZE,0,
									Alpha,0
								),
								Enum.EasingDirection.Out, Enum.EasingStyle.Linear, UPDATE_WAIT, true
							)

							if Color then
								if ColorType == "Color3" then
									Bar.BackgroundColor3 = Color
								elseif ColorType == "ColorSequence" then
									Bar.BackgroundColor3 = AlphaColorSequence(Color, Alpha)
								end
							end

						end
					end
				end)
				if not success then Visualizer:UnlinkFromSound() end
			end)
		else
			table.clear(Visualizer.VolumeBuffer)
			for i,Bar in ipairs(Visualizer.Bars) do
				Bar:TweenSize(
					UDim2.new(BAR_SIZE,0,0.02,0),
					Enum.EasingDirection.Out, Enum.EasingStyle.Linear, UPDATE_WAIT, true
				)
			end
		end
	end

	function Visualizer:LinkToSound(Sound)

		Visualizer:UnlinkFromSound()

		table.clear(Visualizer.VolumeBuffer)

		Visualizer.Sound = Sound

		HandlePlaying()
		Visualizer.PlayingConnection = Sound:GetPropertyChangedSignal("Playing"):Connect(HandlePlaying)
	end

	function Visualizer:UnlinkFromSound()

		if Visualizer.PlayingConnection then
			Visualizer.PlayingConnection:Disconnect()
			Visualizer.PlayingConnection = nil
		end
		if Visualizer.BufferConnection then
			Visualizer.BufferConnection:Disconnect()
			Visualizer.BufferConnection = nil
		end

		Visualizer.Sound = nil

		for i,Bar in ipairs(Visualizer.Bars) do
			Bar:TweenSize(
				UDim2.new(BAR_SIZE,0,0.02,0),
				Enum.EasingDirection.Out, Enum.EasingStyle.Linear, UPDATE_WAIT, true
			)
		end

	end

	function Visualizer:Destroy()
		if Visualizer.PlayingConnection then
			Visualizer.PlayingConnection:Disconnect()
			Visualizer.PlayingConnection = nil
		end
		if Visualizer.BufferConnection then
			Visualizer.BufferConnection:Disconnect()
			Visualizer.BufferConnection = nil
		end
		for i,Bar in ipairs(Visualizer.Bars) do
			Bar:Destroy()
		end
		table.clear(Visualizer.VolumeBuffer)
	end

	return Visualizer
end

return module


local AdonisModernComponents = script.Parent
local Packages = AdonisModernComponents.Parent.Packages

local Roact = require(Packages.Roact)

local function SortProps(Props)
	table.sort(Props, function(A, B)
		return A.Name < B.Name
	end)
end

local function ParseDocument(Document: string)

	local Summary = ''
	local RequiredProps = {}
	local OptionalProps = {}
	local Styles = {}

	Summary = Document:split('\n')[1]

	for _, Line in pairs(Document:split('\n')) do
		if Line:find('@prop') then
			Line = Line:gsub('@prop', '')

			-- Optional
			local Target = RequiredProps
			if Line:find('@optional') then
				Line = Line:gsub('@optional', '')
				Target = OptionalProps
			end

			-- Style
			if Line:find('@style') then
				Line = Line:gsub('@style', '')
				Target = Styles
			end

			-- Parse
			local Name, Type, Comment = Line:match('([%w%p]+)%s([%w%p]+)%s(.+)')

			if not (Name and Type and Comment) then
				warn('Invalid prop tag:' .. Line)
				continue
			end

			table.insert(Target, {
				Name = Name,
				Type = Type,
				Comment = Comment
			})
		end
	end

	-- Sort the props
	SortProps(RequiredProps)
	SortProps(OptionalProps)

	return {
		Summary = Summary,
		Docs = {
			Required = RequiredProps,
			Optional = OptionalProps,
			Style = Styles
		}
	}
end

return {
	name = 'Adonis Modern Theme',
	roact = Roact,
	storyRoots = {
		script.Parent
	},
	mapDefinition = function (Story)

		-- Parse the document
		local Parsed = ParseDocument(Story.source.Parent:FindFirstChild((Story.source.Name::string):gsub('.story', '')).Source)

		Story.summary = Parsed.Summary
		Story.docs = Parsed.Docs

		return Story
	end
}


local Components = script.Parent
local Packages = Components.Parent.Packages

local Maid = require(Packages.Maid)
local Flipper = require(Packages.Flipper)
local Roact = require(Packages.Roact)
local new = Roact.createElement

-- ////////// AnimationWrapper for Message component

local AnimationWrapper = Roact.PureComponent:extend(script.Name)

function AnimationWrapper:init()
	local props = self.props

	self.Maid = Maid.new()
	self.CurrentAnimationState = 'Rest'

	self.TransparencyMotor = Flipper.SingleMotor.new(1)
	self.Transparency, self.SetTransparency = Roact.createBinding(1)

	self.Maid.TransparencyMotor = function()
		self.TransparencyMotor:destroy()
	end

	-- ////////// Animation

	self.TransparencyMotor:onStep(function(Value)
		self.SetTransparency(Value)
	end)

	self.TransparencyMotor:onComplete(function()
		if self.CurrentAnimationState == 'FadeOut' then
			props.OnFadeOutCompleted()
		end

		self.CurrentAnimationState = 'Rest'
	end)

	self.Maid.FadeIn = props.FadeInSignal:Connect(function()
		self.CurrentAnimationState = 'FadeIn'
		self.TransparencyMotor:setGoal(Flipper.Linear.new(0, {
			velocity = props.FadeInVelocity,
		}))
	end)

	self.Maid.FadeOut = props.FadeOutSignal:Connect(function()
		self.CurrentAnimationState = 'FadeOut'
		self.TransparencyMotor:setGoal(Flipper.Linear.new(1, {
			velocity = props.FadeOutVelocity,
		}))
	end)
end

function AnimationWrapper:willUnmount()
	self.Maid:Destroy()
end

function AnimationWrapper:render()
	local props = self.props

	return new('CanvasGroup', {
		GroupTransparency = self.Transparency,
		BackgroundTransparency = 1,
		Size = UDim2.fromScale(1, 1),
	}, props[Roact.Children])
end

return AnimationWrapper


-- Hint: Subtle hint appearing on the top of the screen

local Components = script.Parent
local Packages = Components.Parent.Packages

local t = require(Packages.t)
local Roact = require(Packages.Roact)
local new = Roact.createElement

local Hint = Roact.PureComponent:extend(script.Name)

Hint.validateProps = t.strictInterface({

	--- @prop @optional Image string Image
	Image = t.optional(t.string),

	--- @prop @optional TitleText string Title text
	TitleText = t.optional(t.string),

	--- @prop @optional BodyText string Message content
	BodyText = t.optional(t.string),
})

local FONT_FACE = Font.fromId(12187364147)
local FONT_SIZE = 18

function Hint:render()
	local props = self.props

	self.HasThumbnailAsImage = (props.Image and props.Image:find("rbxthumb")) and true or false
	self.HasAvatarThumbnailAsImage = (
		self.HasThumbnailAsImage and (props.Image:match("type=Avatar") or props.Image:match("type=HeadShot"))
	)
	and true or false

	return new("Frame", {
		AutomaticSize = Enum.AutomaticSize.Y,
		BackgroundColor3 = Color3.fromRGB(43, 43, 43),
		BackgroundTransparency = 0.2,
		BorderSizePixel = 0,
		Size = UDim2.fromScale(1, 0),
	}, {

		Layout = new("UIListLayout", {
			Padding = UDim.new(0, 10),
			FillDirection = Enum.FillDirection.Horizontal,
			HorizontalAlignment = Enum.HorizontalAlignment.Center,
			VerticalAlignment = Enum.VerticalAlignment.Center,
		}),

		Padding = new("UIPadding", {
			PaddingBottom = UDim.new(0, 5),
			PaddingTop = UDim.new(0, 5),
		}),

		Text = new("Frame", {
			AutomaticSize = Enum.AutomaticSize.XY,
			BackgroundTransparency = 1,
			LayoutOrder = 1,
		}, {

			Layout = new("UIListLayout", {
				FillDirection = Enum.FillDirection.Vertical,
				HorizontalAlignment = Enum.HorizontalAlignment.Left,
				SortOrder = Enum.SortOrder.LayoutOrder,
				VerticalAlignment = Enum.VerticalAlignment.Top,
			}),

			SizeConstraint = new("UISizeConstraint", {
				MaxSize = Vector2.new(500, math.huge),
			}),

			Body = props.BodyText and new("TextLabel", {
				AutomaticSize = Enum.AutomaticSize.XY,
				BackgroundTransparency = 1,
				LayoutOrder = 1,
				FontFace = FONT_FACE,
				RichText = true,
				Text = props.BodyText,
				TextColor3 = Color3.fromRGB(255, 255, 255),
				TextSize = FONT_SIZE,
				TextWrapped = true,
				TextXAlignment = Enum.TextXAlignment.Left,
				TextYAlignment = Enum.TextYAlignment.Top,
			}),

			Title = props.TitleText and new("TextLabel", {
				AutomaticSize = Enum.AutomaticSize.XY,
				BackgroundTransparency = 1,
				LayoutOrder = 0,
				FontFace = FONT_FACE,
				RichText = true,
				Text = props.TitleText,
				TextColor3 = Color3.fromRGB(255, 255, 255),
				TextSize = FONT_SIZE,
				TextXAlignment = Enum.TextXAlignment.Left,
				TextYAlignment = Enum.TextYAlignment.Top,
			}),
		}),

		Image = props.Image and new("ImageLabel", {
			BackgroundColor3 = Color3.fromRGB(45, 45, 45),
			BackgroundTransparency = self.HasThumbnailAsImage and 0 or 1,
			Size = UDim2.fromOffset(48/2, 48/2),
			Image = props.Image,
		}, {

			Corner = self.HasThumbnailAsImage and new("UICorner", {
				CornerRadius = UDim.new(
					self.HasAvatarThumbnailAsImage and 0.5 or 0,
					(not self.HasAvatarThumbnailAsImage) and 4 or 0
				),
			}) or nil,
		}),
	})
end

return Hint


local Components = script.Parent
local Packages = Components.Parent.Packages

local Roact = require(Packages.Roact)
local new = Roact.createElement

local Hint = require(script.Parent.Hint)
local HintStory = Roact.PureComponent:extend(script.Name)

function HintStory:render()
	local props = self.props
	local Image = props.Image and "rbxthumb://type=AvatarHeadShot&id=649787117&w=48&h=48" or nil

	if Image and not props.ThumbnailAsImage then
		Image = "rbxassetid://8303332379"
	end

	return new(Hint, {
		TitleText = props.Title and "<i>subtle</i> hint" or nil,
		BodyText = "Hints shouldnt have that much text",
		Image = Image,
	})
end

return {
	controls = {
		Title = false,
		ThumbnailAsImage = true,
		Image = true,
	},
	story = function(StoryProps)
		return new(HintStory, StoryProps.controls)
	end,
}


-- Message: Attention grabbing on screen message

local Components = script.Parent
local Packages = Components.Parent.Packages

local t = require(Packages.t)
local Roact = require(Packages.Roact)
local new = Roact.createElement

local Message = Roact.PureComponent:extend(script.Name)

Message.validateProps = t.strictInterface({

	--- @prop @optional Image string Image
	Image = t.optional(t.string),

	--- @prop @optional TitleText string Title text
	TitleText = t.optional(t.string),

	--- @prop @optional BodyText string Message content
	BodyText = t.optional(t.string),
})

function Message:render()
	local props = self.props

	self.HasThumbnailAsImage = (props.Image and props.Image:find('rbxthumb')) and true or false
	self.HasAvatarThumbnailAsImage = (self.HasThumbnailAsImage and (
		props.Image:match('type=Avatar') or
		props.Image:match('type=HeadShot')
	)) and true or false

	return new('Frame', {
		AnchorPoint = props.Condensed and Vector2.new(0.5, 0) or Vector2.new(0, 0.5),
		AutomaticSize = props.Condensed and Enum.AutomaticSize.XY or Enum.AutomaticSize.Y,
		BackgroundColor3 = Color3.fromRGB(0, 0, 0),
		BackgroundTransparency = 0.2,
		BorderSizePixel = 0,
		Position = props.Condensed and UDim2.new(0.5, 0, 0, 150) or UDim2.fromScale(0, 0.5),
		Size = props.Condensed and UDim2.fromScale(0.5, 0.5) or UDim2.fromScale(1, 0),
	}, {

		Layout = new('UIListLayout', {
			Padding = UDim.new(0, 15),
			FillDirection = Enum.FillDirection.Horizontal,
			HorizontalAlignment = Enum.HorizontalAlignment.Center,
			VerticalAlignment = Enum.VerticalAlignment.Center,
		}),

		Padding = new('UIPadding', {
			PaddingBottom = UDim.new(0, 15),
			PaddingTop = UDim.new(0, 15),
			PaddingLeft = UDim.new(0, props.Condensed and 15 or 50),
			PaddingRight = UDim.new(0, props.Condensed and 15 or 50),
		}),

		Corners = props.Condensed and new('UICorner', {
			CornerRadius = UDim.new(0, 8),
		}) or nil,

		Text = new('Frame', {
			AutomaticSize = Enum.AutomaticSize.XY,
			BackgroundTransparency = 1,
			LayoutOrder = 1,
		}, {

			Layout = new('UIListLayout', {
				Padding = UDim.new(0, 5),
				FillDirection = Enum.FillDirection.Vertical,
				HorizontalAlignment = props.Image and Enum.HorizontalAlignment.Left or Enum.HorizontalAlignment.Center,
				SortOrder = Enum.SortOrder.LayoutOrder,
				VerticalAlignment = Enum.VerticalAlignment.Top,
			}),

			SizeConstraint = new('UISizeConstraint', {
				MaxSize = Vector2.new(500, math.huge),
			}),

			Body = props.BodyText and new('TextLabel', {
				AutomaticSize = Enum.AutomaticSize.XY,
				BackgroundTransparency = 1,
				LayoutOrder = 1,
				FontFace = Font.fromEnum(Enum.Font.SourceSans),
				RichText = true,
				Text = props.BodyText,
				TextColor3 = Color3.fromRGB(255, 255, 255),
				TextSize = 18,
				TextWrapped = true,
				TextXAlignment = Enum.TextXAlignment.Left,
				TextYAlignment = Enum.TextYAlignment.Top,
			}),

			Title = props.TitleText and new('TextLabel', {
				AutomaticSize = Enum.AutomaticSize.XY,
				BackgroundTransparency = 1,
				LayoutOrder = 0,
				FontFace = Font.fromEnum(Enum.Font.SourceSansBold),
				RichText = true,
				Text = props.TitleText,
				TextColor3 = Color3.fromRGB(255, 255, 255),
				TextSize = 18,
				TextXAlignment = Enum.TextXAlignment.Left,
				TextYAlignment = Enum.TextYAlignment.Top,
			}),
		}),

		Image = props.Image and new('ImageLabel', {
			BackgroundColor3 = Color3.fromRGB(45, 45, 45),
			BackgroundTransparency = self.HasThumbnailAsImage and 0 or 1,
			Size = UDim2.fromOffset(48, 48),
			Image = props.Image,
		}, {

			Corner = self.HasThumbnailAsImage and new('UICorner', {
				CornerRadius = UDim.new(self.HasAvatarThumbnailAsImage and 0.5 or 0, (not self.HasAvatarThumbnailAsImage) and 4 or 0),
			}) or nil,
		}),
	})
end

return Message


local Components = script.Parent
local Packages = Components.Parent.Packages

local Roact = require(Packages.Roact)
local new = Roact.createElement

local Message = require(script.Parent.Message)
local MessageStory = Roact.PureComponent:extend(script.Name)

function MessageStory:render()
	local props = self.props
	local Image = props.Image and 'rbxthumb://type=AvatarHeadShot&id=649787117&w=48&h=48' or nil

	if Image and not props.ThumbnailAsImage then
		Image = 'rbxassetid://8303332379'
	end

	return new(Message, {
		TitleText = props.TitleText or 'Message from <b>omwot</b>',
		BodyText = 'Dont we all love text wrapping because it is so great and this text should be wrapped, if it is not wrapped then something is wrong or it is just bad design. We all know about the things that we have to do but this is just some nonsense text in order to test some stuff. Blah blah blah is all i hear right now because yes and the yeaaou aou afouwa elf oug.',
		Image = Image,
		Condensed = props.Condensed,
	})
end

return {
	controls = {
		ThumbnailAsImage = true,
		Image = true,
	},
	stories = {
		Message = function(props)
			return new(MessageStory, props.controls)
		end,
		SmallMessage = function(props)
			local controlprops = table.clone(props.controls)
			controlprops.Condensed = true
			return new(MessageStory, controlprops)
		end
	}
}