local e,F,w,s,i,u,S,M,K,G=table,ipairs,string,next,pcall,game,workspace,tostring,tonumber,getmetatable local P=w.format local y=w.char local R=w.gsub local J=w.match local t=w.rep local X=w.sub local o=w.gmatch local N=e.find local p=e.concat local W=e.insert local U=typeof or type local f do local e={"\097\110\100";"\098\114\101\097\107","\100\111";"\101\108\115\101";"\101\108\115\101\105\102","\101\110\100","\102\097\108\115\101";"\102\111\114";"\102\117\110\099\116\105\111\110","\105\102","\105\110","\108\111\099\097\108","\110\105\108";"\110\111\116";"\111\114";"\114\101\112\101\097\116";"\114\101\116\117\114\110","\116\104\101\110";"\116\114\117\101";"\117\110\116\105\108";"\119\104\105\108\101","\099\111\110\116\105\110\117\101"}f=setmetatable({},{__mode="\107"})for e,F in s,e do f[F]=true end end N=N or function(e,w)for e,F in F(e)do if F==w then return e end end return end local function d(e,F,...)local w={...}local s=U(e)if not N(w,s)then error(P("\076\117\097\069\110\099\111\100\101\058 \073\110\099\111\114\114\101\099\116 \116\121\112\101 \102\111\114 \096\037\115\096\058 \096\037\115\096 \101\120\112\101\099\116\101\100\044 \103\111\116 \096\037\115\096",F,p(w,"\044 "),s),0)end return e end local r do local e={["\""]="\092\"",["\092"]="\092\092",["\a"]="\092\097";["\b"]="\092\098",["\t"]="\092\116";["\n"]="\092\110";["\v"]="\092\118",["\012"]="\092\102";["\r"]="\092\114"}for F=0,255,1 do local w=y(F)if not e[w]and(F<32 or F>126)then e[w]="\092"..F end end function r(F)return"\""..(R(F,"\091\037\122\092\"\001-\031\127-\255\093",e).."\"")end end local function g(e)local F=""for e in o(e,"\037\093\040\061\042\041\037\093")do if#e>=#F then F=e.."\061"end end return"--\091"..(F..("\091"..(e..("\093"..(F.."\093")))))end local V do local function e(e)local F,w=i(u.FindService,u,e.ClassName)if F and w then return true end return false end function V(F)local w=F if not w then return end local s=""while w do local F=w.Name local i=w.ClassName local M=w.Parent if M==u and e(w)then s="\058\071\101\116\083\101\114\118\105\099\101\040"..(r(i)..("\041"..s))elseif not f[F]and J(F,"\094\091\065-\090\097-\122\095\093\091\065-\090\097-\122\048-\057\095\093\042\036")then s="\046"..(F..s)else s="\091"..(r(F)..("\093"..s))end if M==u then s="\103\097\109\101"..s return s elseif M==S then s="\119\111\114\107\115\112\097\099\101"..s return s end w=M end return end end local function E(e,F)d(e,"\105\110\112\117\116\084\097\098\108\101","\116\097\098\108\101")d(F,"\111\112\116\105\111\110\115","\116\097\098\108\101","\110\105\108")if F then d(F.Prettify,"\111\112\116\105\111\110\115\046\080\114\101\116\116\105\102\121","\098\111\111\108\101\097\110","\110\105\108")d(F.PrettyPrinting,"\111\112\116\105\111\110\115\046\080\114\101\116\116\121\080\114\105\110\116\105\110\103","\098\111\111\108\101\097\110","\110\105\108")d(F.IndentCount,"\111\112\116\105\111\110\115\046\073\110\100\101\110\116\067\111\117\110\116","\110\117\109\098\101\114","\110\105\108")d(F.OutputWarnings,"\111\112\116\105\111\110\115\046\079\117\116\112\117\116\087\097\114\110\105\110\103\115","\098\111\111\108\101\097\110","\110\105\108")d(F.StackLimit,"\111\112\116\105\111\110\115\046\083\116\097\099\107\076\105\109\105\116","\110\117\109\098\101\114","\110\105\108")d(F.FunctionsReturnRaw,"\111\112\116\105\111\110\115\046\070\117\110\099\116\105\111\110\115\082\101\116\117\114\110\082\097\119","\098\111\111\108\101\097\110","\110\105\108")d(F.UseInstancePaths,"\111\112\116\105\111\110\115\046\085\115\101\073\110\115\116\097\110\099\101\080\097\116\104\115","\098\111\111\108\101\097\110","\110\105\108")d(F.SerializeMathHuge,"\111\112\116\105\111\110\115\046\083\101\114\105\097\108\105\122\101\077\097\116\104\072\117\103\101","\098\111\111\108\101\097\110","\110\105\108")d(F._StackLevel,"\111\112\116\105\111\110\115\046\095\083\116\097\099\107\076\101\118\101\108","\110\117\109\098\101\114","\110\105\108")d(F._VisitedTables,"\111\112\116\105\111\110\115\046\095\083\116\097\099\107\076\101\118\101\108","\116\097\098\108\101","\110\105\108")end F=F or{}local w=F.Prettify==nil and(F.PrettyPrinting==nil and false)or F.Prettify~=nil and F.Prettify or F.PrettyPrinting and F.PrettyPrinting local u=F.IndentCount or w and 4 or 0 local S=F.OutputWarnings==nil and true or F.OutputWarnings local y=F.StackLimit or 500 local R=F.FunctionsReturnRaw==nil and false or F.FunctionsReturnRaw local o=F.UseInstancePaths==nil and true or F.UseInstancePaths local N=F.SerializeMathHuge==nil and true or F.SerializeMathHuge local b=F._StackLevel or 1 local l=F._VisitedTables or{[e]=true}if b>=y then return"\123--\091\091\076\117\097\069\110\099\111\100\101\058 \083\116\097\099\107 \108\101\118\101\108 \108\105\109\105\116 \111\102 \096"..(y.."\096 \114\101\097\099\104\101\100\093\093\125")end local q=N and"\109\097\116\104\046\104\117\103\101"or"\049\047\048"local I=N and"-\109\097\116\104\046\104\117\103\101"or"-\049\047\048"local Q=w and"\n"or""local T=w and"\044 "or"\044"local m=w and" "or""local Z=t(" ",u)Z=w and t(Z,b)or Z local H=#Z>0 and X(Z,1,-u-1)or""local C=1 local k={}do local function i(e,F)local w=k[e](F,false)return w end local function S(...)local e={}for F,w in s,{...}do W(e,i(U(w),w))end return p(e,T)end local function y(e,F)return"\040\102\117\110\099\116\105\111\110\040\118\044 \112\041 \102\111\114 \112\110\044 \112\118 \105\110 \110\101\120\116\044 \112 \100\111 \118\091\112\110\093 \061 \112\118 \101\110\100 \114\101\116\117\114\110 \118 \101\110\100\041\040"..(p({e,i("\116\097\098\108\101",F)},T).."\041")end k.number=function(e,F)if F and e==C then C=C+1 return nil,true end if e==1/0 then return q elseif e==-1/0 then return I end return P("\037\046\049\052\103",e)end k.string=function(e,F)if F and(not f[e]and J(e,"\094\091\065-\090\097-\122\095\093\091\065-\090\097-\122\048-\057\095\093\042\036"))then return e,true end return r(e)end k.table=function(s,i)do local F=l[s]if F then return P("\123--\091\091\076\117\097\069\110\099\111\100\101\058 \068\117\112\108\105\099\097\116\101 \114\101\102\101\114\101\110\099\101\037\115\093\093\125",s==e and" \040\111\102 \112\097\114\101\110\116\041"or"")end l[s]=true end local S=setmetatable({},{__index=F})do S.Prettify=i and false or w S.IndentCount=i and(not w and u or 1)or u S._StackLevel=i and 1 or b+1 S._VisitedTables=l end return E(s,S)end k.boolean=function(e)return e and"\116\114\117\101"or"\102\097\108\115\101"end k["\110\105\108"]=function(e)return"\110\105\108"end k["\102\117\110\099\116\105\111\110"]=function(e)if R then return e()end return"\102\117\110\099\116\105\111\110\040\041 --\091\091\076\117\097\069\110\099\111\100\101\058 \096\111\112\116\105\111\110\115\046\070\117\110\099\116\105\111\110\115\082\101\116\117\114\110\082\097\119\096 \102\097\108\115\101\059 \099\097\110\'\116 \115\101\114\105\097\108\105\122\101 \102\117\110\099\116\105\111\110\115\093\093 \114\101\116\117\114\110 \101\110\100"end k.Axes=function(e)local F={}local w={["\069\110\117\109\046\065\120\105\115\046\088"]=e.X,["\069\110\117\109\046\065\120\105\115\046\089"]=e.Y;["\069\110\117\109\046\065\120\105\115\046\090"]=e.Z}for e,w in s,w do if w then W(F,e)end end return"\065\120\101\115\046\110\101\119\040"..(p(F,T).."\041")end k.BrickColor=function(e)return"\066\114\105\099\107\067\111\108\111\114\046\110\101\119\040"..(w and i("\115\116\114\105\110\103",e.Name))or e.Number.."\041"end k.CFrame=function(e)return"\067\070\114\097\109\101\046\110\101\119\040"..(S(e:components()).."\041")end k.CatalogSearchParams=function(e)return y("\067\097\116\097\108\111\103\083\101\097\114\099\104\080\097\114\097\109\115\046\110\101\119\040\041",{SearchKeyword=e.SearchKeyword,MinPrice=e.MinPrice;MaxPrice=e.MaxPrice;SortType=e.SortType;CategoryFilter=e.CategoryFilter;BundleTypes=e.BundleTypes;AssetTypes=e.AssetTypes})end k.Color3=function(e)return"\067\111\108\111\114\051\046\110\101\119\040"..S(e.R,e.G,e.B)end k.ColorSequence=function(e)return"\067\111\108\111\114\083\101\113\117\101\110\099\101\046\110\101\119\040"..(i("\116\097\098\108\101",e.Keypoints).."\041")end k.ColorSequenceKeypoint=function(e)return"\067\111\108\111\114\083\101\113\117\101\110\099\101\075\101\121\112\111\105\110\116\046\110\101\119\040"..(S(e.Time,e.Value).."\041")end k.DateTime=function(e)return"\068\097\116\101\084\105\109\101\046\102\114\111\109\085\110\105\120\084\105\109\101\115\116\097\109\112\040"..(e.UnixTimestamp.."\041")end k.DockWidgetPluginGuiInfo=function(e)local F=M(e)return"\068\111\099\107\087\105\100\103\101\116\080\108\117\103\105\110\071\117\105\073\110\102\111\046\110\101\119\040"..(S(Enum.InitialDockState[J(F,"\073\110\105\116\105\097\108\068\111\099\107\083\116\097\116\101\058\040\037\119\043\041")],J(F,"\073\110\105\116\105\097\108\069\110\097\098\108\101\100\058\040\037\119\043\041")=="\049",J(F,"\073\110\105\116\105\097\108\069\110\097\098\108\101\100\083\104\111\117\108\100\079\118\101\114\114\105\100\101\082\101\115\116\111\114\101\058\040\037\119\043\041")=="\049",K(J(F,"\070\108\111\097\116\105\110\103\088\083\105\122\101\058\040\037\119\043\041")),K(J(F,"\070\108\111\097\116\105\110\103\089\083\105\122\101\058\040\037\119\043\041")),K(J(F,"\077\105\110\087\105\100\116\104\058\040\037\119\043\041")),K(J(F,"\077\105\110\072\101\105\103\104\116\058\040\037\119\043\041"))).."\041")end k.Enum=function(e)return"\069\110\117\109\046"..M(e)end k.EnumItem=function(e)return M(e)end k.Enums=function(e)return"\069\110\117\109"end k.Faces=function(e)local F={}local w={["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\084\111\112"]=e.Top;["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\066\111\116\116\111\109"]=e.Bottom,["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\076\101\102\116"]=e.Left,["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\082\105\103\104\116"]=e.Right,["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\066\097\099\107"]=e.Back;["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\070\114\111\110\116"]=e.Front}for e,w in s,w do if w then W(F,e)end end return"\070\097\099\101\115\046\110\101\119\040"..(p(F,T).."\041")end k.FloatCurveKey=function(e)return"\070\108\111\097\116\067\117\114\118\101\075\101\121\046\110\101\119\040"..(S(e.Time,e.Value,e.Interpolation).."\041")end k.Font=function(e)return"\070\111\110\116\046\110\101\119\040"..(S(e.Family,e.Weight,e.Style).."\041")end k.Instance=function(e)if o then local F=V(e)if F then return F end end return"\110\105\108"..(m..g("\073\110\115\116\097\110\099\101\046\110\101\119\040"..(i("\115\116\114\105\110\103",e.ClassName).."\041")))end k.NumberRange=function(e)return"\078\117\109\098\101\114\082\097\110\103\101\046\110\101\119\040"..(S(e.Min,e.Max).."\041")end k.NumberSequence=function(e)return"\078\117\109\098\101\114\083\101\113\117\101\110\099\101\046\110\101\119\040"..(i("\116\097\098\108\101",e.Keypoints).."\041")end k.NumberSequenceKeypoint=function(e)return"\078\117\109\098\101\114\083\101\113\117\101\110\099\101\075\101\121\112\111\105\110\116\046\110\101\119\040"..(S(e.Time,e.Value,e.Envelope).."\041")end k.OverlapParams=function(e)return y("\079\118\101\114\108\097\112\080\097\114\097\109\115\046\110\101\119\040\041",{FilterDescendantsInstances=e.FilterDescendantsInstances;FilterType=e.FilterType,MaxParts=e.MaxParts,CollisionGroup=e.CollisionGroup,RespectCanCollide=e.RespectCanCollide})end k.PathWaypoint=function(e)return"\080\097\116\104\087\097\121\112\111\105\110\116\046\110\101\119\040"..(S(e.Position,e.Action,e.Label).."\041")end k.PhysicalProperties=function(e)return"\080\104\121\115\105\099\097\108\080\114\111\112\101\114\116\105\101\115\046\110\101\119\040"..(S(e.Density,e.Friction,e.Elasticity,e.FrictionWeight,e.ElasticityWeight).."\041")end k.Random=function()return"\082\097\110\100\111\109\046\110\101\119\040\041"end k.Ray=function(e)return"\082\097\121\046\110\101\119\040"..(S(e.Origin,e.Direction).."\041")end k.RaycastParams=function(e)return y("\082\097\121\099\097\115\116\080\097\114\097\109\115\046\110\101\119\040\041",{FilterDescendantsInstances=e.FilterDescendantsInstances,FilterType=e.FilterType,IgnoreWater=e.IgnoreWater;CollisionGroup=e.CollisionGroup;RespectCanCollide=e.RespectCanCollide})end k.Rect=function(e)return"\082\101\099\116\046\110\101\119\040"..(S(e.Min,e.Max).."\041")end k.Region3=function(e)local F=e.CFrame local w=e.Size return"\082\101\103\105\111\110\051\046\110\101\119\040"..(S(F*CFrame.new(-w/2),F*CFrame.new(w/2)).."\041")end k.Region3int16=function(e)return"\082\101\103\105\111\110\051\105\110\116\049\054\046\110\101\119\040"..(S(e.Min,e.Max).."\041")end k.TweenInfo=function(e)return"\084\119\101\101\110\073\110\102\111\046\110\101\119\040"..(S(e.Time,e.EasingStyle,e.EasingDirection,e.RepeatCount,e.Reverses,e.DelayTime).."\041")end k.RotationCurveKey=function(e)return"\082\111\116\097\116\105\111\110\067\117\114\118\101\075\101\121\046\110\101\119\040"..(S(e.Time,e.Value,e.Interpolation).."\041")end k.UDim=function(e)return"\085\068\105\109\046\110\101\119\040"..(S(e.Scale,e.Offset).."\041")end k.UDim2=function(e)return"\085\068\105\109\050\046\110\101\119\040"..(S(e.X.Scale,e.X.Offset,e.Y.Scale,e.Y.Offset).."\041")end k.Vector2=function(e)return"\086\101\099\116\111\114\050\046\110\101\119\040"..(S(e.X,e.Y).."\041")end k.Vector2int16=function(e)return"\086\101\099\116\111\114\050\105\110\116\049\054\046\110\101\119\040"..(S(e.X,e.Y).."\041")end k.Vector3=function(e)return"\086\101\099\116\111\114\051\046\110\101\119\040"..(S(e.X,e.Y,e.Z).."\041")end k.Vector3int16=function(e)return"\086\101\099\116\111\114\051\105\110\116\049\054\046\110\101\119\040"..(S(e.X,e.Y,e.Z).."\041")end k.userdata=function(e)if G(e)then return"\110\101\119\112\114\111\120\121\040\116\114\117\101\041"else return"\110\101\119\112\114\111\120\121\040\041"end end end local Y=""for F,u in s,e do local M=U(F)local K=U(u)if k[M]and k[K]then local G=w and Q..Z or""local y=false local R,J,t=i(k[M],F,true)local X,o=i(k[K],u,false)if R and(X and o)then local e=J and(t and J or P("\091\037\115\093",J))..(w and" \061 "or"\061")or""G=G..(e..o)y=true elseif S then local e=P("\076\117\097\069\110\099\111\100\101\058 \070\097\105\108\101\100 \116\111 \101\110\099\111\100\101 \037\115 \111\102 \068\097\116\097\084\121\112\101 \096\037\115\096\058 \037\115",not R and"\107\101\121"or not X and"\118\097\108\117\101"or"\107\101\121\047\118\097\108\117\101",K,not R and r(J)or not X and r(o)or"\040\070\097\105\108\101\100 \116\111 \103\101\116 \101\114\114\111\114 \109\101\115\115\097\103\101\041")G=G..g(e)end if s(e,F)then if y then G=G.."\044"end else G=G..(Q..H)end Y=Y..G end end Y="\123"..(Y.."\125")return Y end return E
-- LuaEncode - Optimal Table Serialization for Native Luau/Lua 5.1+
-- Copyright (c) 2022-2023 Reggie <reggie@latte.to> | MIT License
-- https://github.com/regginator/LuaEncode

--!nocheck
--!optimize 2

-- Localizing certain libraries/variables used throughout for runtime efficiency (not specific to Luau)
local table, ipairs, string, next, pcall, game, workspace, tostring, tonumber, getmetatable =
	table, ipairs, string, next, pcall, game, workspace, tostring, tonumber, getmetatable

local string_format = string.format
local string_char = string.char
local string_gsub = string.gsub
local string_match = string.match
local string_rep = string.rep
local string_sub = string.sub
local string_gmatch = string.gmatch

local table_find = table.find
local table_concat = table.concat
local table_insert = table.insert

-- For custom Roblox engine data-type support via `typeof`, if it exists
local Type = typeof or type

-- Used for checking direct getfield syntax; Lua keywords can't be used as keys without being a str
-- FYI; `continue` is Luau only (in Lua it's actually a global function), but we're including it
-- here anyway to be safe
local LuaKeywords
do
	local LuaKeywordsArray = {
		"and",
		"break",
		"do",
		"else",
		"elseif",
		"end",
		"false",
		"for",
		"function",
		"if",
		"in",
		"local",
		"nil",
		"not",
		"or",
		"repeat",
		"return",
		"then",
		"true",
		"until",
		"while",
		"continue",
	}

	-- We're now setting each keyword str to a weak key, so it's faster at runtime for `SerializeString()`
	LuaKeywords = setmetatable({}, { __mode = "k" })

	for _, Keyword in next, LuaKeywordsArray do
		LuaKeywords[Keyword] = true
	end
end

-- Lua 5.1 doesn't have table.find
table_find = table_find
	or function(inputTable, valueToFind) -- Ignoring the `init` arg, unneeded for us
		for Key, Value in ipairs(inputTable) do
			if Value == valueToFind then
				return Key -- Return the key idx
			end
		end

		return
	end

-- Simple function for directly checking the type on values, with their input, variable name,
-- and desired type name(s) to check
local function CheckType(inputData, dataName, ...)
	local DesiredTypes = { ... }
	local InputDataType = Type(inputData)

	if not table_find(DesiredTypes, InputDataType) then
		error(
			string_format(
				"LuaEncode: Incorrect type for `%s`: `%s` expected, got `%s`",
				dataName,
				table_concat(DesiredTypes, ", "), -- For if multiple types are accepted
				InputDataType
			),
			0
		)
	end

	return inputData -- Return back input directly
end

-- This re-serializes a string back into Lua, for the interpreter AND humans to read. This fixes
-- `string_format("%q")` only outputting in system encoding, instead of explicit Lua byte escapes
local SerializeString
do
	-- These are control characters to be encoded in a certain way in Lua rather than just a byte
	-- escape (e.g. "\n" -> "\10")
	local SpecialCharacters = {
		['"'] = '\\"', -- Double-Quote
		["\\"] = "\\\\", -- (Literal) Backslash
		-- Special ASCII control char codes
		["\a"] = "\\a", -- Bell; ASCII #7
		["\b"] = "\\b", -- Backspace; ASCII #8
		["\t"] = "\\t", -- Horizontal-Tab; ASCII #9
		["\n"] = "\\n", -- Newline; ASCII #10
		["\v"] = "\\v", -- Vertical-Tab; ASCII #11
		["\f"] = "\\f", -- Form-Feed; ASCII #12
		["\r"] = "\\r", -- Carriage-Return; ASCII #13
	}

	-- We need to assign all extra normal byte escapes for runtime optimization
	for Index = 0, 255 do
		local Character = string_char(Index)

		if not SpecialCharacters[Character] and (Index < 32 or Index > 126) then
			SpecialCharacters[Character] = "\\" .. Index
		end
	end

	function SerializeString(inputString)
		-- FYI; We can't do "\0-\31" in Lua 5.1 (Only Luau/Lua 5.2+) due to an embedded zeros in pattern
		-- issue. See: https://stackoverflow.com/a/22962409
		return '"' .. string_gsub(inputString, '[%z\\"\1-\31\127-\255]', SpecialCharacters) .. '"'
	end
end

-- We need to occasionally construct valid comment blocks from external input, with proper escapes etc
local function CommentBlock(inputString)
	local Padding = ""
	for Match in string_gmatch(inputString, "%](=*)%]") do
		if #Match >= #Padding then
			Padding = Match .. "="
		end
	end

	return "--[" .. Padding .. "[" .. inputString .. "]" .. Padding .. "]"
end

local EvaluateInstancePath
do
	-- VERY simple function to get if an object is a service, used in instance path eval
	local function IsService(object)
		-- Logically, if an object is actually under a service, that service *has* to already exist, as we've
		-- presumably evaluated to said path
		local FindServiceSuccess, ServiceObject = pcall(game.FindService, game, object.ClassName)

		if FindServiceSuccess and ServiceObject then
			return true
		end

		return false
	end

	-- Evaluating an instances' accessable "path" with just it's ref, and if the root parent is nil/isn't
	-- under `game` or `workspace`, returns nil.
	function EvaluateInstancePath(object)
		-- "Recursive" eval
		local ObjectPointer = object

		-- Input itself doesn't exist?
		if not ObjectPointer then
			return
		end

		local Path = ""

		while ObjectPointer do
			local ObjectName = ObjectPointer.Name
			local ObjectClassName = ObjectPointer.ClassName
			local ObjectParent = ObjectPointer.Parent

			if ObjectParent == game and IsService(ObjectPointer) then
				-- ^^ Then we'll use GetService directly, since it's actually a service under the DataModel

				Path = ":GetService(" .. SerializeString(ObjectClassName) .. ")" .. Path
			elseif not LuaKeywords[ObjectName] and string_match(ObjectName, "^[A-Za-z_][A-Za-z0-9_]*$") then
				-- ^^ Like the `string` DataType, this means means we can index the name directly in Lua
				-- without an explicit string
				Path = "." .. ObjectName .. Path
			else
				Path = "[" .. SerializeString(ObjectName) .. "]" .. Path
			end

			if ObjectParent == game then
				Path = "game" .. Path
				return Path
			elseif ObjectParent == workspace then
				Path = "workspace" .. Path
				return Path
			end

			-- Advance ObjectPointer, whether it exists or not (JUMPBACK)
			ObjectPointer = ObjectParent
		end

		-- Fall back to no ret.. Only objects parented under game/workspace will be serialized
		return
	end
end

--[[
LuaEncode(inputTable: {[any]: any}, options: {[string]: any}): string

    ---------- OPTIONS: ----------

    Prettify <boolean?:false> | Whether or not the output should use pretty printing

    IndentCount <number?:0> | The amount of "spaces" that should be indented per entry (*Note:
    If `Prettify` is set to true and this is unspecified, it'll be set to `4` automatically*)

    OutputWarnings <boolean?:true> | If "warnings" should be placed to the output (as
    comments); It's recommended to keep this enabled, however this can be disabled at ease

    StackLimit <number?:500> | The limit to the stack level before recursive encoding cuts
    off, and stops execution. This is used to prevent stack overflow errors and such. You
    could use `math.huge` here if you really wanted

    FunctionsReturnRaw <boolean?:false> | If functions in said table return back a "raw"
    value to place in the output as the key/value

    UseInstancePaths <boolean?:true> | If `Instance` reference objects should return their
    Lua-accessable path for encoding. If the instance is parented under `nil` or isn't under
    `game`/`workspace`, it'll always fall back to `Instance.new(ClassName)` as before

    SerializeMathHuge <boolean?:true> | If numbers calculated as "infinite" (or negative-inf)
    numbers should be serialized with "math.huge". (uses the `math` import, as opposed to just
    a direct data type) If false, "`1/0`" or "`-1/0`" will be serialized, which is supported
    on all target versions

]]

local function LuaEncode(inputTable, options)
	-- Check all arg and option types
	CheckType(inputTable, "inputTable", "table") -- Required*, nil not allowed
	CheckType(options, "options", "table", "nil") -- `options` is an optional arg

	-- Options
	if options then
		CheckType(options.Prettify, "options.Prettify", "boolean", "nil")
		CheckType(options.PrettyPrinting, "options.PrettyPrinting", "boolean", "nil") -- Alias for `Options.Prettify`
		CheckType(options.IndentCount, "options.IndentCount", "number", "nil")
		CheckType(options.OutputWarnings, "options.OutputWarnings", "boolean", "nil")
		CheckType(options.StackLimit, "options.StackLimit", "number", "nil")
		CheckType(options.FunctionsReturnRaw, "options.FunctionsReturnRaw", "boolean", "nil")
		CheckType(options.UseInstancePaths, "options.UseInstancePaths", "boolean", "nil")
		CheckType(options.SerializeMathHuge, "options.SerializeMathHuge", "boolean", "nil")

		-- Internal options:
		CheckType(options._StackLevel, "options._StackLevel", "number", "nil")
		CheckType(options._VisitedTables, "options._StackLevel", "table", "nil")
	end

	options = options or {}

	-- Because no if-else-then exp. in Lua 5.1+ (only Luau), for optional boolean values we need to check
	-- if it's nil first, THEN fall back to whatever it's actually set to if it's not nil
	local Prettify = (options.Prettify == nil and options.PrettyPrinting == nil and false)
		or (options.Prettify ~= nil and options.Prettify)
		or (options.PrettyPrinting and options.PrettyPrinting)
	local IndentCount = options.IndentCount or (Prettify and 4) or 0
	local OutputWarnings = (options.OutputWarnings == nil and true) or options.OutputWarnings
	local StackLimit = options.StackLimit or 500
	local FunctionsReturnRaw = (options.FunctionsReturnRaw == nil and false) or options.FunctionsReturnRaw
	local UseInstancePaths = (options.UseInstancePaths == nil and true) or options.UseInstancePaths
	local SerializeMathHuge = (options.SerializeMathHuge == nil and true) or options.SerializeMathHuge

	-- Internal options:

	-- Internal stack level for depth checks and indenting
	local StackLevel = options._StackLevel or 1
	-- Set root as visited; cyclic detection
	local VisitedTables = options._VisitedTables or { [inputTable] = true } -- [`visitedTable <table>`] = `isVisited <boolean>`

	-- Stack overflow/output abuse etc; default StackLimit is 500
	if StackLevel >= StackLimit then
		return "{--[[LuaEncode: Stack level limit of `" .. StackLimit .. "` reached]]}"
	end

	-- For +/- inf num serialization
	local PositiveInf = (SerializeMathHuge and "math.huge") or "1/0"
	local NegativeInf = (SerializeMathHuge and "-math.huge") or "-1/0"

	-- Easy-to-reference values for specific args
	local NewEntryString = (Prettify and "\n") or ""
	local ValueSeperator = (Prettify and ", ") or ","
	local BlankSeperator = (Prettify and " ") or ""

	-- For pretty printing (which is optional, and false by default) we need to keep track
	-- of the current stack, then repeat IndentString by that count
	local IndentString = string_rep(" ", IndentCount) -- If 0 this will just be ""
	IndentString = (Prettify and string_rep(IndentString, StackLevel)) or IndentString

	local EndingIndentString = (#IndentString > 0 and string_sub(IndentString, 1, -IndentCount - 1)) or ""

	-- For number key values, incrementing the current internal index
	local KeyIndex = 1

	-- Cases (C-Like) for encoding values, then end setup. Using cases so no elseif bs!
	-- Functions are all expected to return a (<string> EncodedKey, <boolean?> EncloseInBrackets)
	local TypeCases = {}
	do
		-- Basic func for getting the direct value of an encoded type without weird table.pack()[1] syntax
		local function TypeCase(typeName, value)
			-- Each of these funcs return a tuple, so it'd be annoying to do case-by-case
			local EncodedValue = TypeCases[typeName](value, false) -- False to label as NOT `isKey`
			return EncodedValue
		end

		-- For "tuple" args specifically, so there isn't a bunch of re-used code
		local function Args(...)
			local EncodedValues = {}

			for _, Arg in next, { ... } do
				table_insert(EncodedValues, TypeCase(Type(Arg), Arg))
			end

			return table_concat(EncodedValues, ValueSeperator)
		end

		-- For certain Roblox DataTypes, we use a custom serialization method for filling out params etc
		local function Params(newData, params)
			return "(function(v, p) for pn, pv in next, p do v[pn] = pv end return v end)("
				.. table_concat({ newData, TypeCase("table", params) }, ValueSeperator)
				.. ")"
		end

		TypeCases["number"] = function(value, isKey)
			-- If the number isn't the current real index of the table, we DO want to
			-- explicitly define it in the serialization no matter what for accuracy
			if isKey and value == KeyIndex then
				-- ^^ What's EXPECTED unless otherwise explicitly defined, if so, return no encoded num
				KeyIndex = KeyIndex + 1
				return nil, true
			end

			-- Lua's internal `tostring` handling will denote positive/negativie-infinite number TValues as "inf", which
			-- makes certain numbers not encode properly. We also just want to make the output precise
			if value == 1 / 0 then
				return PositiveInf
			elseif value == -1 / 0 then
				return NegativeInf
			end

			-- Return fixed-formatted precision num
			return string_format("%.14g", value)
		end

		TypeCases["string"] = function(value, isKey)
			if isKey and not LuaKeywords[value] and string_match(value, "^[A-Za-z_][A-Za-z0-9_]*$") then
				-- ^^ Then it's a syntaxically-correct variable, doesn't need explicit string def
				return value, true
			end

			return SerializeString(value)
		end

		TypeCases["table"] = function(value, isKey)
			-- Check duplicate/cyclic references
			do
				local VisitedTable = VisitedTables[value]
				if VisitedTable then
					return string_format(
						"{--[[LuaEncode: Duplicate reference%s]]}",
						(value == inputTable and " (of parent)") or ""
					)
				end

				VisitedTables[value] = true
			end

			-- *Point index not set by NewOptions to original
			local NewOptions = setmetatable({}, { __index = options })
			do
				-- Overriding if key because it'd look worse pretty printed in a key
				NewOptions.Prettify = (isKey and false) or Prettify

				-- If Prettify is already false in the real args, set the indent to whatever
				-- the REAL IndentCount is set to
				NewOptions.IndentCount = (isKey and ((not Prettify and IndentCount) or 1)) or IndentCount

				-- Internal options
				NewOptions._StackLevel = (isKey and 1) or StackLevel + 1 -- If isKey, stack lvl is set to the **LOWEST** because it's the key to a value
				NewOptions._VisitedTables = VisitedTables
			end

			return LuaEncode(value, NewOptions)
		end

		TypeCases["boolean"] = function(value)
			return value and "true" or "false"
		end

		TypeCases["nil"] = function(value)
			return "nil"
		end

		TypeCases["function"] = function(value)
			-- If `FunctionsReturnRaw` is set as true, we'll call the function here itself, expecting
			-- a raw value for FunctionsReturnRaw to add as the key/value, you may want to do this for custom userdata or
			-- function closures. Thank's for listening to my Ted Talk!
			if FunctionsReturnRaw then
				return value()
			end

			-- If all else, force key func to return nil; can't handle a func val..
			return "function() --[[LuaEncode: `options.FunctionsReturnRaw` false; can't serialize functions]] return end"
		end

		---------- ROBLOX CUSTOM DATATYPES BELOW ----------

		TypeCases["Axes"] = function(value)
			local EncodedArgs = {}
			local EnumValues = {
				["Enum.Axis.X"] = value.X,
				["Enum.Axis.Y"] = value.Y,
				["Enum.Axis.Z"] = value.Z,
			}

			for EnumValue, IsEnabled in next, EnumValues do
				if IsEnabled then
					table_insert(EncodedArgs, EnumValue)
				end
			end

			return "Axes.new(" .. table_concat(EncodedArgs, ValueSeperator) .. ")"
		end

		TypeCases["BrickColor"] = function(value)
			-- BrickColor.Number (Its enum ID) will be slightly more efficient in all cases in deser,
			-- so we'll use it if Options.Prettify is false
			return "BrickColor.new(" .. (Prettify and TypeCase("string", value.Name)) or value.Number .. ")"
		end

		TypeCases["CFrame"] = function(value)
			return "CFrame.new(" .. Args(value:components()) .. ")"
		end

		TypeCases["CatalogSearchParams"] = function(value)
			return Params("CatalogSearchParams.new()", {
				SearchKeyword = value.SearchKeyword,
				MinPrice = value.MinPrice,
				MaxPrice = value.MaxPrice,
				SortType = value.SortType, -- EnumItem
				CategoryFilter = value.CategoryFilter, -- EnumItem
				BundleTypes = value.BundleTypes, -- table
				AssetTypes = value.AssetTypes, -- table
			})
		end

		TypeCases["Color3"] = function(value)
			-- Using floats for RGB values, most accurate for direct serialization
			return "Color3.new(" .. Args(value.R, value.G, value.B)
		end

		TypeCases["ColorSequence"] = function(value)
			return "ColorSequence.new(" .. TypeCase("table", value.Keypoints) .. ")"
		end

		TypeCases["ColorSequenceKeypoint"] = function(value)
			return "ColorSequenceKeypoint.new(" .. Args(value.Time, value.Value) .. ")"
		end

		-- We're using fromUnixTimestamp to serialize the object
		TypeCases["DateTime"] = function(value)
			-- Always an int, we don't need to do anything special
			return "DateTime.fromUnixTimestamp(" .. value.UnixTimestamp .. ")"
		end

		-- Properties seem to throw an error on index if the scope isn't a Studio plugin, so we're
		-- directly getting values! (so fun!!!!)
		TypeCases["DockWidgetPluginGuiInfo"] = function(value)
			-- e.g.: "InitialDockState:Right InitialEnabled:0 InitialEnabledShouldOverrideRestore:0 FloatingXSize:0 FloatingYSize:0 MinWidth:0 MinHeight:0"
			local ValueString = tostring(value)

			return "DockWidgetPluginGuiInfo.new("
				.. Args(
					-- InitialDockState (Enum.InitialDockState)
					Enum.InitialDockState[string_match(ValueString, "InitialDockState:(%w+)")], -- Enum.InitialDockState.Right
					-- InitialEnabled and InitialEnabledShouldOverrideRestore (boolean as number; `0` or `1`)
					string_match(ValueString, "InitialEnabled:(%w+)") == "1", -- false
					string_match(ValueString, "InitialEnabledShouldOverrideRestore:(%w+)") == "1", -- false
					-- FloatingXSize/FloatingYSize (numbers)
					tonumber(string_match(ValueString, "FloatingXSize:(%w+)")), -- 0
					tonumber(string_match(ValueString, "FloatingYSize:(%w+)")), -- 0
					-- MinWidth/MinHeight (numbers)
					tonumber(string_match(ValueString, "MinWidth:(%w+)")), -- 0
					tonumber(string_match(ValueString, "MinHeight:(%w+)")) -- 0
				)
				.. ")"
		end

		-- e.g. `Enum.UserInputType`
		TypeCases["Enum"] = function(value)
			return "Enum." .. tostring(value) -- For now, this is the behavior of enums in tostring.. I have no other choice atm
		end

		-- e.g. `Enum.UserInputType.Gyro`
		TypeCases["EnumItem"] = function(value)
			return tostring(value) -- Returns the full enum index for now (e.g. "Enum.UserInputType.Gyro")
		end

		-- i.e. the `Enum` global return
		TypeCases["Enums"] = function(value)
			return "Enum"
		end

		TypeCases["Faces"] = function(value)
			local EncodedArgs = {}
			local EnumValues = {
				["Enum.NormalId.Top"] = value.Top, -- These return bools
				["Enum.NormalId.Bottom"] = value.Bottom,
				["Enum.NormalId.Left"] = value.Left,
				["Enum.NormalId.Right"] = value.Right,
				["Enum.NormalId.Back"] = value.Back,
				["Enum.NormalId.Front"] = value.Front,
			}

			for EnumValue, IsEnabled in next, EnumValues do
				if IsEnabled then
					table_insert(EncodedArgs, EnumValue)
				end
			end

			return "Faces.new(" .. table_concat(EncodedArgs, ValueSeperator) .. ")"
		end

		TypeCases["FloatCurveKey"] = function(value)
			return "FloatCurveKey.new(" .. Args(value.Time, value.Value, value.Interpolation) .. ")"
		end

		TypeCases["Font"] = function(value)
			return "Font.new(" .. Args(value.Family, value.Weight, value.Style) .. ")"
		end

		-- Instance refs can be evaluated to their paths (optional), but if parented to
		-- nil or some DataModel not under `game`, it'll just return nil
		TypeCases["Instance"] = function(value)
			if UseInstancePaths then
				local InstancePath = EvaluateInstancePath(value)
				if InstancePath then
					return InstancePath
				end

				-- ^^ Now, if the path isn't accessable, falls back to the return below anyway
			end

			return "nil"
				.. BlankSeperator
				.. CommentBlock("Instance.new(" .. TypeCase("string", value.ClassName) .. ")")
		end

		TypeCases["NumberRange"] = function(value)
			return "NumberRange.new(" .. Args(value.Min, value.Max) .. ")"
		end

		TypeCases["NumberSequence"] = function(value)
			return "NumberSequence.new(" .. TypeCase("table", value.Keypoints) .. ")"
		end

		TypeCases["NumberSequenceKeypoint"] = function(value)
			return "NumberSequenceKeypoint.new(" .. Args(value.Time, value.Value, value.Envelope) .. ")"
		end

		TypeCases["OverlapParams"] = function(value)
			return Params("OverlapParams.new()", {
				FilterDescendantsInstances = value.FilterDescendantsInstances,
				FilterType = value.FilterType,
				MaxParts = value.MaxParts,
				CollisionGroup = value.CollisionGroup,
				RespectCanCollide = value.RespectCanCollide,
			})
		end

		TypeCases["PathWaypoint"] = function(value)
			return "PathWaypoint.new(" .. Args(value.Position, value.Action, value.Label) .. ")"
		end

		TypeCases["PhysicalProperties"] = function(value)
			return "PhysicalProperties.new("
				.. Args(value.Density, value.Friction, value.Elasticity, value.FrictionWeight, value.ElasticityWeight)
				.. ")"
		end

		TypeCases["Random"] = function()
			return "Random.new()"
		end

		TypeCases["Ray"] = function(value)
			return "Ray.new(" .. Args(value.Origin, value.Direction) .. ")"
		end

		TypeCases["RaycastParams"] = function(value)
			return Params("RaycastParams.new()", {
				FilterDescendantsInstances = value.FilterDescendantsInstances,
				FilterType = value.FilterType,
				IgnoreWater = value.IgnoreWater,
				CollisionGroup = value.CollisionGroup,
				RespectCanCollide = value.RespectCanCollide,
			})
		end

		TypeCases["Rect"] = function(value)
			return "Rect.new(" .. Args(value.Min, value.Max) .. ")"
		end

		-- Roblox doesn't provide read properties for min/max on `Region3`, but they do on Region3int16.. Anyway,
		-- we CAN calculate the min/max of a Region3 from just .CFrame and .Size.. Thanks to wally for linking me
		-- the thread for this method lol
		TypeCases["Region3"] = function(value)
			local ValueCFrame = value.CFrame
			local ValueSize = value.Size

			return "Region3.new("
				.. Args(
					ValueCFrame * CFrame.new(-ValueSize / 2), -- Minimum
					ValueCFrame * CFrame.new(ValueSize / 2) -- Maximum
				)
				.. ")"
		end

		TypeCases["Region3int16"] = function(value)
			return "Region3int16.new(" .. Args(value.Min, value.Max) .. ")"
		end

		TypeCases["TweenInfo"] = function(value)
			return "TweenInfo.new("
				.. Args(
					value.Time,
					value.EasingStyle,
					value.EasingDirection,
					value.RepeatCount,
					value.Reverses,
					value.DelayTime
				)
				.. ")"
		end

		-- CURRENTLY UNDOCUMENTED*
		TypeCases["RotationCurveKey"] = function(value)
			return "RotationCurveKey.new(" .. Args(value.Time, value.Value, value.Interpolation) .. ")"
		end

		TypeCases["UDim"] = function(value)
			return "UDim.new(" .. Args(value.Scale, value.Offset) .. ")"
		end

		TypeCases["UDim2"] = function(value)
			return "UDim2.new("
				.. Args(
					-- Not directly using X and Y UDims for better output (i.e. would be
					-- UDim2.new(UDim.new(1, 0), UDim.new(1, 0)) if I did)
					value.X.Scale,
					value.X.Offset,
					value.Y.Scale,
					value.Y.Offset
				)
				.. ")"
		end

		TypeCases["Vector2"] = function(value)
			return "Vector2.new(" .. Args(value.X, value.Y) .. ")"
		end

		TypeCases["Vector2int16"] = function(value)
			return "Vector2int16.new(" .. Args(value.X, value.Y) .. ")"
		end

		TypeCases["Vector3"] = function(value)
			return "Vector3.new(" .. Args(value.X, value.Y, value.Z) .. ")"
		end

		TypeCases["Vector3int16"] = function(value)
			return "Vector3int16.new(" .. Args(value.X, value.Y, value.Z) .. ")"
		end

		-- With userdata, just encode directly
		TypeCases["userdata"] = function(value)
			if getmetatable(value) then -- Has mt
				return "newproxy(true)"
			else
				return "newproxy()" -- newproxy() defaults to false (no mt)
			end
		end
	end

	-- Setup output tbl
	local Output = ""

	for Key, Value in next, inputTable do
		local KeyType = Type(Key)
		local ValueType = Type(Value)

		if TypeCases[KeyType] and TypeCases[ValueType] then
			local EntryOutput = (Prettify and NewEntryString .. IndentString) or ""
			local ValueWasEncoded = false -- Keeping track of this for adding a "," to the EntryOutput if needed

			-- Go through and get key val
			local KeyEncodedSuccess, EncodedKeyOrError, DontEncloseInBrackets = pcall(TypeCases[KeyType], Key, true) -- The `true` represents if it's a key or not, here it is

			-- Ignoring 2nd arg (`DontEncloseInBrackets`) because this isn't the key
			local ValueEncodedSuccess, EncodedValueOrError = pcall(TypeCases[ValueType], Value, false) -- `false` because it's NOT the key, it's the value

			-- Im sorry for this logic chain here, I can't use `continue`/`continue()`.. :sob:
			-- Ignoring `if EncodedKeyOrError` because the key doesn't actually need to ALWAYS
			-- be explicitly encoded, like if it's a number of the current key index!
			if KeyEncodedSuccess and ValueEncodedSuccess and EncodedValueOrError then
				-- NOW we'll check for if the key was explicitly encoded, because we don't to stop
				-- the value from encoding, since we've already checked that and it *has* been
				local KeyValue = EncodedKeyOrError
						and ((DontEncloseInBrackets and EncodedKeyOrError) or string_format("[%s]", EncodedKeyOrError)) .. ((Prettify and " = ") or "=")
					or ""

				-- Encode key/value together, we've already checked if `EncodedValueOrError` was returned
				EntryOutput = EntryOutput .. KeyValue .. EncodedValueOrError
				ValueWasEncoded = true
			elseif OutputWarnings then -- Then `Encoded(Key/Value)OrError` is the error msg
				-- ^^ Then either the key or value wasn't properly checked or encoded, and there
				-- was an error we need to log!
				local ErrorMessage = string_format(
					"LuaEncode: Failed to encode %s of DataType `%s`: %s",
					(not KeyEncodedSuccess and "key") or (not ValueEncodedSuccess and "value") or "key/value", -- "key/value" for bool type fallback
					ValueType,
					(not KeyEncodedSuccess and SerializeString(EncodedKeyOrError))
						or (not ValueEncodedSuccess and SerializeString(EncodedValueOrError))
						or "(Failed to get error message)"
				)

				EntryOutput = EntryOutput .. CommentBlock(ErrorMessage)
			end

			-- If there isn't another value after the current index, add ending formatting
			if next(inputTable, Key) then
				-- Yes.. The nesting here is deliberate
				if ValueWasEncoded then
					EntryOutput = EntryOutput .. ","
				end
			else
				-- If there isn't another value after the current index, add ending formatting
				EntryOutput = EntryOutput .. NewEntryString .. EndingIndentString
			end

			Output = Output .. EntryOutput
		end
	end

	Output = "{" .. Output .. "}"
	return Output
end

return LuaEncode
