-- Tests for tree viewer

local FUZZ_TESTS = 25

local Theme = require(script.Parent.Parent.UI.Theme)
local Tree = require(script.Parent.Parent.UI.Viewers.Tree)

local fuzzUserIds = require(script.fuzzUserIds)
local fuzzJSON = require(script.fuzzJSON)

type SearchForQuery = { key: string | number, value: any?, isContainer: boolean? }
type Operation = {
	type: "delete",
	searchFor: SearchForQuery,
	onlyChildren: boolean?,
} | {
	type: "insertInto",
	searchFor: SearchForQuery,
	key: string?,
	value: any,
	isArray: boolean?,
} | {
	type: "insertAfter",
	searchFor: SearchForQuery,
	key: string?,
	value: any,
	isArray: boolean?,
} | {
	type: "move",
	move: SearchForQuery,
	before: SearchForQuery,
} | {
	type: "moveEnd",
	move: SearchForQuery,
} | {
	type: "edit",
	searchFor: SearchForQuery,
	value: any,
	isArray: boolean?,
}

local fakeKeyInfo = {
	CreatedTime = 0,
	UpdatedTime = 0,
	UserIds = {},
	Version = "",
	Metadata = nil,
}

local testTree1 = {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			6,
			6,
			"slayy",
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}

local testTree2 = {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			hello = 5,
			taxi = 3,
			nested = {
				hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
				aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
				moreNEsted = {
					3,
					4,
					5,
					6,
					6,
					"slayy",
					{
						"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
						43,
						{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
					},
					{
						hello = 5,
						taxi = 3,
						nested = {
							hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
							aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
							moreNEsted = {
								3,
								4,
								5,
								6,
								6,
								"slayy",
								{
									"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
									43,
									{
										"Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus",
										45,
										true,
										false,
									},
								},
							},
						},
						car = 34,
						tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
						city = false,
						abc = true,
					},
				},
			},
			car = 34,
			tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
			city = false,
			abc = true,
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}

local testTree3 = {
	1,
	2,
	3,
	nil,
	4,
	5,
	nil,
	nil,
	nil,
	nil,
	nil,
	6,
}

local testTree4 = {
	{ 1, 2, 3 },
	{ 4, 5, 6 },
	{ 7, 8, 9 },
	{ 10, 11, 12 },
}

local testTree5 = { 1 / 0 }

local testTree6 = {
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			"slayy",
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
			6,
			6,
		},
	},
}

-- Utilities

local function buffersEqual(b1: buffer, b2: buffer)
	return buffer.tostring(b1) == buffer.tostring(b2)
end

local function tablesEqual(a, b): boolean
	for i, v in a do
		if typeof(v) == "table" then
			if (not b[i]) or (not tablesEqual(v, b[i])) then
				print("NOT EQUAL AT (A1)", "B:", b[i], "A:", v, "KEY:", i)
				return false
			end
		elseif typeof(v) == "buffer" then
			return buffersEqual(v, b[i])
		elseif v ~= b[i] then
			print("NOT EQUAL AT (A2)", "B:", b[i], "A:", v, "KEY:", i)
			return false
		end
	end
	for i, v in b do
		if typeof(v) == "table" then
			if (not a[i]) or (not tablesEqual(v, a[i])) then
				print("NOT EQUAL AT (B1)", "A:", a[i], "B:", v, "KEY:", i)
				return false
			end
		elseif typeof(v) == "buffer" then
			return buffersEqual(v, a[i])
		elseif v ~= a[i] then
			print("NOT EQUAL AT (B2)", "A:", a[i], "B:", v, "KEY:", i)
			return false
		end
	end
	return true
end

local function equals(a, b): boolean
	if typeof(a) ~= typeof(b) then
		return false
	elseif typeof(a) == "table" then
		return tablesEqual(a, b)
	elseif typeof(a) == "buffer" then
		return buffersEqual(a, b)
	else
		return a == b
	end
end

local function searchFor(tree, query: SearchForQuery)
	local atData = false
	for i, branch in tree._flatBranches do
		if not atData then
			atData = branch.ISROOT
			if not atData then
				continue
			end
		end
		if
			branch[1] == query.key
			and (query.value == nil or branch[2] == query.value)
			and (if query.isContainer then typeof(branch[2]) == "userdata" else true)
		then
			return i
		end
	end
end

local function runOperation(tree, operation: Operation)
	if operation.type == "delete" then
		tree:_deleteBranch({
			index = searchFor(tree, operation.searchFor),
			onlyChildren = operation.onlyChildren,
			pushAction = not operation.onlyChildren,
		})
	elseif operation.type == "insertAfter" then
		local index = searchFor(tree, operation.searchFor)
		tree:_digExpand(index)
		tree:_tryInsertAfter({
			index = index,
			key = operation.key,
			value = { value = operation.value, isArray = operation.isArray },
			pushAction = true,
		})
	elseif operation.type == "insertInto" then
		local index = searchFor(tree, operation.searchFor)
		tree:_digExpand(index)
		tree:_tryInsertInto({
			index = index,
			key = operation.key,
			value = { value = operation.value, isArray = operation.isArray },
			pushAction = true,
		})
	elseif operation.type == "move" then
		local moveIndex = searchFor(tree, operation.move)
		local beforeIndex = searchFor(tree, operation.before)
		tree:_digExpand(moveIndex)
		tree:_moveArrayElement({
			branchIndex = moveIndex,
			branchVisibleIndex = table.find(tree._visibleIndexes, moveIndex),
			moveBeforeIndex = beforeIndex,
			moveBeforeVisibleIndex = table.find(tree._visibleIndexes, beforeIndex),
			pushAction = true,
		})
	elseif operation.type == "moveEnd" then
		local moveIndex = searchFor(tree, operation.move)
		tree:_digExpand(moveIndex)
		tree:_moveArrayElementToEnd({
			branchIndex = moveIndex,
			branchVisibleIndex = table.find(tree._visibleIndexes, moveIndex),
			pushAction = true,
		})
	elseif operation.type == "edit" then
		local index = searchFor(tree, operation.searchFor)
		tree:_digExpand(index)
		tree:_editValue({
			index = index,
			value = { value = operation.value, isArray = operation.isArray },
			pushAction = true,
		})
	end
end

local function testOutput(name: string, data, expected, operations: { Operation })
	local function aux(showKeyInfo, searchTerm)
		local name = `{name} showKeyInfo:{showKeyInfo} searchTerm:{searchTerm}`
		local viewer
		local passed = false
		local success = xpcall(function()
			local fake = script.Parent.Parent.UI.Assets.Views.EditKey.Container.Content:Clone()
			local keyInfo = table.clone(fakeKeyInfo)
			keyInfo.UserIds = fuzzUserIds()
			if typeof(data) == "table" then
				keyInfo.Metadata = data
			else
				keyInfo.Metadata = {data}
			end

			viewer = Tree.new(Theme.global, {}, fake, {
				rootName = "test",
				data = data,
				keyInfo = keyInfo,
				isTesting = true,
				showKeyInfo = showKeyInfo,
			})

			if searchTerm then
				viewer:setSearch(searchTerm)
			end

			for _, operation in operations do
				if typeof(operation) ~= "table" then
					continue
				end

				runOperation(viewer, operation)
			end

			local value = viewer:getValue().value
			local userIds, metadata = viewer:getKeyInfo()
			if not equals(expected, value) then
				warn(`{name} failed`)
				warn("got:", value)
				warn("expected:", expected)
				return
			end

			if showKeyInfo then
				if not tablesEqual(userIds, keyInfo.UserIds) then
					warn(`userIds {name} failed`)
					warn("got:", userIds)
					warn("expected:", expected)
					return
				end
				if not tablesEqual(metadata, keyInfo.Metadata) then
					warn(`metadata {name} failed`)
					warn("got:", metadata)
					warn("expected:", expected)
					return
				end
			else
				if not tablesEqual(userIds, {}) then
					warn(`userIds {name} failed`)
					warn("got:", userIds)
					warn("expected to be empty")
				end
				if not tablesEqual(metadata, {}) then
					warn(`metadata {name} failed`)
					warn("got:", metadata)
					warn("expected to be empty")
				end
			end

			passed = true
		end, function(err)
			warn(`{name} errored. Show Key Info: {showKeyInfo}`, err, debug.traceback(nil, 2))
		end)

		if not success then
			warn("value:", data)
			warn("dump:", viewer:_dump())
		elseif passed and #operations == 1 and not operations.skipUndoRedo then
			viewer:undo()
			local undoed = viewer:getValue().value
			if not equals(undoed, data) then
				warn({ `{name} Undo failed` })
				warn("value:", undoed)
				warn("expected:", data)
				return
			end

			viewer:redo()
			local redoed = viewer:getValue().value
			if not equals(redoed, expected) then
				warn({ `{name} Redo failed` })
				warn("value:", redoed)
				warn("expected:", redoed)
				return
			end
		end
	end

	aux(true, nil)
	aux(false, nil)

	aux(true, "test")
	aux(false, "test")

	aux(true, "..................")
	aux(false, "..................")
end

-- Tests

testOutput("Test Preserves 1", testTree1, testTree1, {})
testOutput("Test Preserves 2", testTree2, testTree2, {})
testOutput("Test Preserves 3", testTree3, testTree3, {})
testOutput("Test Preserves 4", testTree4, testTree4, {})
testOutput("Test Preserves 5", testTree5, testTree5, {})
testOutput("Test Preserves 5", testTree6, testTree6, {})

local randomNumber = math.random()
testOutput("Test Preserves String", "test", "test", {})
testOutput("Test Preserves Empty", {}, {}, {})
testOutput("Test Preserves Number", randomNumber, randomNumber, {})
testOutput("Test Preserves Bool", false, false, {})
testOutput("Test Preserves Nil", nil, nil, {})
testOutput("Test Preserves Number", 1e+123, 1e+123, {})
testOutput("Test Preserves Buffer", buffer.fromstring("hello\0 world"), buffer.fromstring("hello\0 world"), {})

for i = 1, FUZZ_TESTS do
	local json = fuzzJSON()
	testOutput(`Test Fuzz Preserves {i}`, json, json, {})
end

local fuzz = math.random()

testOutput("Test Insert After 1", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			6,
			6,
			"slayy",
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
		},
		testplzwork = fuzz, -- SHOULD INSERT HERE
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "insertAfter",
		searchFor = { key = "moreNEsted" },
		key = "testplzwork",
		value = fuzz,
	},
})

testOutput("Test Insert After 2", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			6,
			6,
			"slayy",
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
	testplzwork = fuzz, -- SHOULD INSERT HERE
}, {
	{
		type = "insertAfter",
		searchFor = { key = "abc" },
		key = "testplzwork",
		value = fuzz,
	},
})

testOutput("Test Insert After 3", {
	a = 1,
	b = {
		a1 = 2,
		b2 = 3,
		c3 = 4,
	},
}, {
	a = 1,
	b = {
		a1 = 2,
		b2 = 3,
		c3 = 4,
	},
	test = "worked",
}, {
	{
		type = "insertAfter",
		searchFor = { key = "b" },
		key = "test",
		value = "worked",
	},
})

testOutput("Test Insert After 3", {
	1,
	{
		2,
		3,
		4,
	},
}, {
	1,
	{
		2,
		3,
		4,
	},
	10, -- <-- INSERT HERE
}, {
	{
		type = "insertAfter",
		searchFor = { key = 2, isContainer = true },
		key = "Ykoaeksf",
		value = 10,
	},
})

testOutput("Test Insert After 4", {
	1,
	{
		2,
		3,
		4,
		{ { { { {} } } } },
	},
	6,
	7,
	8,
	9,
	10,
}, {
	1,
	{
		2,
		3,
		4,
		{ { { { {} } } } },
	},
	999, -- <-- INSERT HERE
	6,
	7,
	8,
	9,
	10,
}, {
	{
		type = "insertAfter",
		searchFor = { key = 2, isContainer = true },
		key = "Ykoaeksf",
		value = 999,
	},
})

testOutput("Test Insert After 5 (At End)", {
	1,
	{
		2,
		3,
		4,
		{ { { { {} } } } },
	},
	6,
	7,
	8,
	9,
	10,
}, {
	1,
	{
		2,
		3,
		4,
		{ { { { {} } } } },
	},
	6,
	7,
	8,
	9,
	10,
	999, -- <-- INSERT HERE
}, {
	{
		type = "insertAfter",
		searchFor = { key = 7 },
		key = "yayasdfokaksdf",
		value = 999,
	},
})

testOutput("Test Insert Multiple After", {
	1,
	{
		2,
		3,
		4,
		{ { { { {} } } } },
	},
	6,
	7,
	8,
	9,
	10,
}, {
	1,
	{
		2,
		3,
		4,
		{ { { { {} } } } },
	},
	6,
	7,
	8,
	9,
	10,
	testTree4, -- <-- INSERT HERE
}, {
	{
		type = "insertAfter",
		searchFor = { key = 7 },
		key = "yayasdfokaksdf",
		value = testTree4,
	},
})

testOutput("Test Insert Mutliple After 2", {
	a = 1,
	b = {
		a1 = 2,
		b2 = 3,
		c3 = 4,
	},
	c = 10,
}, {
	a = 1,
	b = {
		a1 = 2,
		b2 = 3,
		c3 = 4,
		test = testTree2, -- <-- INSERT HERE
	},
	c = 10,
}, {
	{
		type = "insertAfter",
		searchFor = { key = "c3" },
		key = "test",
		value = testTree2,
	},
})

testOutput("Test Insert Multiple After 3 (Empty)", { {} }, { {}, testTree2 }, {
	{
		type = "insertAfter",
		searchFor = { key = 1 },
		key = "testplzwork",
		value = testTree2,
	},
})

testOutput("Test Insert Multiple After 4 (Sandwich)", { {}, {} }, { {}, testTree2, {} }, {
	{
		type = "insertAfter",
		searchFor = { key = 1 },
		key = "testplzwork",
		value = testTree2,
	},
})

testOutput("Test Insert Multiple After 5 (Sandwich Keys)", { a = {}, c = {} }, { a = {}, b = testTree1, c = {} }, {
	{
		type = "insertAfter",
		searchFor = { key = "a" },
		key = "b",
		value = testTree1,
	},
})

testOutput("Test Insert Mutliple After 6 (Big Test)", {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			hello = 5,
			taxi = 3,
			nested = {
				hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
				aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
				moreNEsted = {
					3,
					4,
					5,
					6,
					6,
					"slayy",
					{
						"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
						43,
						{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
					},
					{
						hello = 5,
						taxi = 3,
						nested = {
							hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
							aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
							moreNEsted = {
								3,
								4,
								5,
								6,
								6,
								"slayy",
								{
									"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
									43,
									{
										"Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus",
										45,
										true,
										false,
									},
								},
							},
						},
						car = 34,
						tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
						city = false,
						abc = true,
					},
				},
			},
			APPLE = 34,
			tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
			city = false,
			abc = true,
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			hello = 5,
			taxi = 3,
			nested = {
				hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
				aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
				moreNEsted = {
					3,
					4,
					5,
					6,
					6,
					"slayy",
					{
						"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
						43,
						{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
					},
					{
						hello = 5,
						taxi = 3,
						nested = {
							hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
							aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
							moreNEsted = {
								3,
								4,
								5,
								6,
								6,
								"slayy",
								{
									"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
									43,
									{
										"Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus",
										45,
										true,
										false,
									},
								},
							},
						},
						car = 34,
						tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
						city = false,
						abc = true,
					},
				},
			},
			APPLE = 34,
			PEAR = testTree2,
			TOMATO = testTree4,
			ONION = testTree3,
			tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
			city = false,
			abc = true,
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "insertAfter",
		searchFor = { key = "APPLE" },
		key = "PEAR",
		value = testTree2,
	},
	{
		type = "insertAfter",
		searchFor = { key = "PEAR" },
		key = "TOMATO",
		value = testTree4,
	},
	{
		type = "insertAfter",
		searchFor = { key = "TOMATO" },
		key = "ONION",
		value = testTree3,
	},
})

testOutput("Test Insert Into 1", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			6,
			6,
			"slayy",
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
		},
		testplzwork = fuzz, -- SHOULD INSERT HERE
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "insertInto",
		searchFor = { key = "nested" },
		key = "testplzwork",
		value = fuzz,
	},
})

testOutput("Test Insert Into 2", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			6,
			6,
			"slayy",
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
		},
	},
	testplzwork = fuzz, -- SHOULD INSERT HERE
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "insertInto",
		searchFor = { key = "Data" },
		key = "testplzwork",
		value = fuzz,
	},
})

testOutput("Test Insert Into 3", {
	1,
	2,
	3,
}, {
	0, -- <-- INSERT HERE
	1,
	2,
	3,
}, {
	{
		type = "insertInto",
		searchFor = { key = "Data" },
		key = "!@$!$!",
		value = 0,
	},
})

testOutput("Test Insert Into 4", {
	1,
	2,
	3,
	{ 4 },
}, {
	1,
	2,
	3,
	{ 0, 4 }, -- <-- INSERT HERE
}, {
	{
		type = "insertInto",
		searchFor = { key = 4, isContainer = true },
		key = "!@$!$!",
		value = 0,
	},
})

testOutput("Test Insert Into 5", {
	1,
	2,
	{
		hey = "stop",
		eating = {
			"my",
			"food",
		},
	},
	3,
}, {
	1,
	2,
	{
		["!@$!$!"] = 0, -- <-- INSERT HERE
		hey = "stop",
		eating = {
			"my",
			"food",
		},
	},
	3,
}, {
	{
		type = "insertInto",
		searchFor = { key = 3, isContainer = true },
		key = "!@$!$!",
		value = 0,
	},
})

testOutput("Test Insert Into 6 (Empty)", {}, { dog = 1 }, {
	{
		type = "insertInto",
		searchFor = { key = "Data" },
		key = "dog",
		value = 1,
	},
})

testOutput("Test Insert Multiple Into 1", {
	1,
	2,
	{
		hey = "stop",
		eating = {
			"my",
			"food",
		},
	},
	3,
}, {
	1,
	2,
	{
		["!@$!$!"] = { "this", "is", "an", "array" }, -- <-- INSERT HERE
		hey = "stop",
		eating = {
			"my",
			"food",
		},
	},
	3,
}, {
	{
		type = "insertInto",
		searchFor = { key = 3, isContainer = true },
		key = "!@$!$!",
		value = { "this", "is", "an", "array" },
		isArray = true,
	},
})

testOutput("Test Insert Multiple Into 2", {
	nested = {
		{ 1, 2, 3 },
		{ test = {} },
		{ 1, 2, 3 },
		{ 1, 2, 3 },
		{ 1, 2, 3 },
	},
}, {
	nested = {
		{ 1, 2, 3 },
		{ test = { hey = testTree2 } },
		{ 1, 2, 3 },
		{ 1, 2, 3 },
		{ 1, 2, 3 },
	},
}, {
	{
		type = "insertInto",
		searchFor = { key = "test" },
		key = "hey",
		value = testTree2,
	},
})

testOutput("Test Insert Multiple Into 3", {
	nested = {
		{ 1, 2, 3 },
		{ test = {} },
		{ 1, 2, 3 },
		{ 1, 2, 3 },
		{ 1, 2, 3 },
	},
}, {
	nested = {
		{ 1, 2, 3 },
		{ test = {} },
		{ 1, 2, 3 },
		{ testTree4, 1, 2, 3 },
		{ 1, 2, 3 },
	},
}, {
	{
		type = "insertInto",
		searchFor = { key = 4 },
		key = "hey",
		value = testTree4,
	},
})

testOutput("Test Insert Multiple Into 4", {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			hello = 5,
			taxi = 3,
			nested = {
				hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
				aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
				moreNEsted = {
					3,
					4,
					5,
					6,
					6,
					"slayy",
					{
						"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
						43,
						{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
					},
					{
						hello = 5,
						taxi = 3,
						nested = {
							hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
							aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
							moreNEsted = {
								3,
								4,
								5,
								6,
								6,
								"slayy",
								{
									"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
									43,
									{
										"Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus",
										45,
										true,
										false,
									},
								},
							},
						},
						car = 34,
						tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
						city = false,
						abc = true,
						yay = {},
					},
				},
			},
			car = 34,
			tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
			city = false,
			abc = true,
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			hello = 5,
			taxi = 3,
			nested = {
				hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
				aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
				moreNEsted = {
					3,
					4,
					5,
					6,
					6,
					"slayy",
					{
						"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
						43,
						{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
					},
					{
						hello = 5,
						taxi = 3,
						nested = {
							hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
							aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
							moreNEsted = {
								3,
								4,
								5,
								6,
								6,
								"slayy",
								{
									"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
									43,
									{
										"Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus",
										45,
										true,
										false,
									},
								},
							},
						},
						car = 34,
						tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
						city = false,
						abc = true,
						yay = { rah = testTree2 },
					},
				},
			},
			car = 34,
			tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
			city = false,
			abc = true,
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "insertInto",
		searchFor = { key = "yay" },
		key = "rah",
		value = testTree2,
	},
})

testOutput("Test Insert Multiple Into 5 (Empty)", {}, {
	yes = testTree5,
}, {
	{
		type = "insertInto",
		searchFor = { key = "Data" },
		key = "yes",
		value = testTree5,
	},
})

--[[testOutput("Test Delete 1", testTree1, nil, {
	{
		type = "delete",
		searchFor = { key = "Data" },
	},
})]]

testOutput("Test Delete 2", testTree3, {}, {
	skipUndoRedo = true,
	{
		type = "delete",
		searchFor = { key = "Data" },
		onlyChildren = true,
	},
})

testOutput("Test Delete 3", testTree1, {
	hello = 5,
	taxi = 3,
	-- DELETED HERE
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "delete",
		searchFor = { key = "nested" },
		onlyChildren = false,
	},
})

testOutput("Test Delete 4", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	skipUndoRedo = true,
	{
		type = "delete",
		searchFor = { key = "nested" },
		onlyChildren = true,
	},
})

testOutput("Test Delete 5", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			6,
			"slayy",
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "delete",
		searchFor = { key = 4 },
		onlyChildren = false,
	},
})

testOutput("Test Delete 6", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			6,
			6,
			"slayy",
			{},
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	skipUndoRedo = true,
	{
		type = "delete",
		searchFor = { key = 7 },
		onlyChildren = true,
	},
})

testOutput("Test Delete 7", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			6,
			6,
			"slayy",
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "delete",
		searchFor = { key = 7 },
		onlyChildren = false,
	},
})

testOutput("Test Move 1", testTree1, {
	hello = 5,
	taxi = 3,
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
			3,
			4,
			5,
			6,
			6,
			"slayy",
		},
	},
	car = 34,
	tree = "wassa w3f aw3f aw3f aw3 aw3 aw3f aw wa3 aw3 aw wa3 aw3 aw3a w3fup",
	city = false,
	abc = true,
}, {
	{
		type = "move",
		move = { key = 7 },
		before = { key = 1 },
	},
})

testOutput("Test Move 2", testTree4, {
	{ 7, 8, 9 },
	{ 1, 2, 3 },
	{ 4, 5, 6 },
	{ 10, 11, 12 },
}, {
	{
		type = "move",
		move = { key = 3, isContainer = true },
		before = { key = 1, isContainer = true },
	},
})

testOutput("Test Move 3", testTree4, {
	{ 1, 2, 3 },
	{ 4, 5, 6 },
	{ 7, 8, 9 },
	{ 10, 12, 11 },
}, {
	{
		type = "move",
		move = { key = 3, value = 12 },
		before = { key = 2, value = 11 },
	},
})

testOutput("Test Move 4", testTree4, {
	{ 4, 5, 6 },
	{ 7, 8, 9 },
	{ 1, 2, 3 },
	{ 10, 11, 12 },
}, {
	{
		type = "move",
		move = { key = 1, isContainer = true },
		before = { key = 4, isContainer = true },
	},
})

testOutput("Test Move 5", testTree4, {
	{ 1, 2, 3 },
	{ 4, 5, 6 },
	{ 7, 8, 9 },
	{ 11, 10, 12 },
}, {
	{
		type = "move",
		move = { key = 1, value = 10 },
		before = { key = 3, value = 12 },
	},
})

testOutput("Test Move End", testTree4, {
	{ 4, 5, 6 },
	{ 7, 8, 9 },
	{ 10, 11, 12 },
	{ 1, 2, 3 },
}, {
	{
		type = "moveEnd",
		move = { key = 1, isContainer = true },
	},
})

testOutput("Test Move End 2", testTree4, {
	{ 1, 2, 3 },
	{ 4, 5, 6 },
	{ 7, 8, 9 },
	{ 11, 12, 10 },
}, {
	{
		type = "moveEnd",
		move = { key = 1, value = 10 },
	},
})

testOutput("Test Move End 3", testTree3, {
	1,
	2,
	3,
	nil,
	5,
	nil,
	nil,
	nil,
	nil,
	nil,
	6,
	4,
}, {
	{
		type = "moveEnd",
		move = { key = 5, value = 4 },
	},
})

testOutput("Test Move End 4", testTree6, {
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			3,
			4,
			5,
			"slayy",
			6,
			6,
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
		},
	},
}, {
	{
		type = "moveEnd",
		move = { key = 5, isContainer = true },
	},
})

testOutput("Test Move End 5", testTree6, {
	nested = {
		hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
		aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
		moreNEsted = {
			4,
			5,
			"slayy",
			{
				"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
				43,
				{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
			},
			6,
			6,
			3,
		},
	},
}, {
	{
		type = "moveEnd",
		move = { key = 1, value = 3 },
	},
})

for i = 1, 3 do
	local fuzz1 = fuzzJSON(0)
	testOutput("Test Edit 1 Fuzz " .. i, testTree6, {
		nested = {
			hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
			aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
			moreNEsted = {
				fuzz1,
				4,
				5,
				"slayy",
				{
					"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
					43,
					{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
				},
				6,
				6,
			},
		},
	}, {
		{
			type = "edit",
			searchFor = { key = 1, value = 3 },
			value = fuzz1,
		},
	})
end
for i = 1, 3 do
	local fuzz1 = fuzzJSON()
	testOutput("Test Edit 2 Fuzz " .. i, testTree6, {
		nested = {
			hello = "y aw3faw3 faw3f aw3 aw3 aw3 faw3 fw3 faw3 aw3 wa3 aw3 aw3f es",
			aeoae = "wasafw 3faw3 awf aw3 aw3 aw3 aw aw aw3 aw3 aw3 aw3 aw3 aw3 aw wa3 aw3 wa3 aw3tt 3asup",
			moreNEsted = {
				fuzz1,
				4,
				5,
				"slayy",
				{
					"wafawefawf awfawefawefawfeawefawfwaef awef awef aewf awef awefssup",
					43,
					{ "Uusfa wefawf waefewa fawfa weafwaef awef awef awfe aw3f awe waaw efus", 45, true, false },
				},
				6,
				6,
			},
		},
	}, {
		{
			type = "edit",
			searchFor = { key = 1, value = 3 },
			value = fuzz1,
		},
	})
end

testOutput("Test Edit 3 Has Children", {
	thisTable = {
		hasChildren = 1,
		its = true,
		its2 = false,
	},
}, {
	thisTable = 3,
}, {
	{
		type = "edit",
		searchFor = { key = "thisTable" },
		value = 3,
	},
})

testOutput("Test Edit 4 Has Children Replace Many", {
	thisTable = {
		hasChildren = 1,
		its = true,
		its2 = false,
	},
}, {
	thisTable = testTree2,
}, {
	{
		type = "edit",
		searchFor = { key = "thisTable" },
		value = testTree2,
	},
})

testOutput("Test Edit 5 Empty", {
	{},
}, {
	5,
}, {
	{
		type = "edit",
		searchFor = { key = 1 },
		value = 5,
	},
})

testOutput("Test Edit 6 Empty Replace Many", {
	{},
}, {
	testTree2,
}, {
	{
		type = "edit",
		searchFor = { key = 1 },
		value = testTree2,
	},
})

testOutput("Test Edit 7 Root", 1234, {
	a = 1,
	b = 2,
}, {
	{
		type = "edit",
		searchFor = { key = "Data" },
		value = {
			a = 1,
			b = 2,
		},
	},
})


local Validators = require(script.Parent.Parent.Validators)

assert(Validators.empty(math.random(100000000)))

assert(Validators.integer(0))
assert(Validators.integer(10))
assert(Validators.integer(-10))
assert(not Validators.integer(0.001))
assert(not Validators.integer(10.1))
assert(not Validators.integer(-10.1))
assert(not Validators.integer(1 / 0))
assert(not Validators.integer(0 / 0))
assert(not Validators.integer(-1 / 0))

assert(not Validators.positiveNonZeroInteger("0"))
assert(not Validators.positiveNonZeroInteger("-1"))
assert(Validators.positiveNonZeroInteger("1"))

assert(Validators.userId(10))
assert(not Validators.userId(0))
assert(Validators.userId(123456))
assert(not Validators.userId(1.5))
assert(not Validators.userId(1241.242424))
assert(not Validators.userId(-1))
assert(not Validators.userId(-1231231))
assert(not Validators.userId(-123.123))


declare wax: {
    version: string,
    envname: string,
    shared: {[any]: any},

    script: LuaSourceContainer,

    require: (...any) -> ...any,
}


local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer
local PlayerGui = LocalPlayer:FindFirstChildWhichIsA("PlayerGui")

local Fusion = require(script.Fusion)

local New = Fusion.New
local Children = Fusion.Children
local Value = Fusion.Value
local Computed = Fusion.Computed

local ValueChanges = Value(0)

task.spawn(function()
    for _ = 1, 50 do
        ValueChanges:set(ValueChanges:get() + 1)
        task.wait(1)
    end
end)

New "ScreenGui" {
    Name = "WaxWithFusion",
    Parent = PlayerGui,

    DisplayOrder = 100,
    ResetOnSpawn = false,

    [Children] = {
        New "TextLabel" {
            Name = "Hello",

            AutomaticSize = Enum.AutomaticSize.XY,
            AnchorPoint = Vector2.new(0.5, 0.5),
            BackgroundColor3 = Color3.fromRGB(44, 56, 63),

            Position = UDim2.fromScale(0.5, 0.5),

            Font = Enum.Font.GothamMedium,
            Text = Computed(function()
                return "Hello, Wax & Fusion!\nTesting.. Testing... " .. ValueChanges:get()
            end),
            TextColor3 = Color3.fromRGB(218, 223, 228),
            TextSize = 16,
            TextWrapped = true,

            [Children] = {
                New "UIPadding" {
                    PaddingTop = UDim.new(0, 16),
                    PaddingBottom = UDim.new(0, 16),
                    PaddingLeft = UDim.new(0, 16),
                    PaddingRight = UDim.new(0, 16),
                },

                New "UICorner" {
                    CornerRadius = UDim.new(0, 4)
                },

                New "UIStroke" {
                    ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
                    Color = Color3.fromRGB(45, 51, 58)
                }
            }
        }
    }
}


local Players = game:GetService("Players")

local React = require(script.ReactLuau.React)
local ReactRoblox = require(script.ReactLuau.ReactRoblox)

local e = React.createElement

local LocalPlayer = Players.LocalPlayer
local PlayerGui = LocalPlayer.PlayerGui

local Element = e("ScreenGui", {}, {
    e("TextLabel", {
        Text = "Hello, Wax & Roact17! (react-roblox)",
        TextSize = 32,
        Font = Enum.Font.Gotham,
        Size = UDim2.fromScale(1, 1),
    })
})

local root = ReactRoblox.createRoot(Instance.new("Folder"))
root:render(ReactRoblox.createPortal(Element, PlayerGui))


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

local luau = require("@lune/luau")
local roblox = require("@lune/roblox")

local LuneUtils = require("./libraries/LuneUtils")

local Log = LuneUtils.Log
local StringUtils = LuneUtils.StringUtils

local LuaEncode = require("./libraries/LuaEncode")

local Version = require("./data/Version")

-- Init script template for codegen (in this, we'll maintain 5.1 *syntax* compat
-- for optional compilation to vanilla Lua 5.1x bytecode format)
local InitScriptTemplate = require("./data/Template")

-- The line where the 1st closure's function will start in bundled codegen
local InitialLineOffset = 8

local ClassNameIdKeys = {
	Folder = 1,
	ModuleScript = 2,
	Script = 3,
	LocalScript = 4,
	StringValue = 5,
}

-- Building codegen directly from model data (from .rbxm/.rbxmx) or a deserialized
-- object using Lune's `roblox` library
local function BundleModel(
	modelData: string | { roblox.Instance },
	envName: string?,
	minifyCodegen: boolean?,
	extraLinesToOffset: number?,
	verbose: boolean?
): (string, number)
	local EnvName = envName or "WaxRuntime"
	local MinifyCodegen = if minifyCodegen == nil then true else minifyCodegen
	local ExtraLinesToOffset = extraLinesToOffset or 0
	local Verbose = if verbose == nil then true else verbose

	-- Same as in Wax CLI
	local function RunIfVerbose(f, ...)
		if Verbose then
			f(...)
		end
	end

	local LineDebugging = MinifyCodegen == false

	local ModelRoot = if type(modelData) == "string" then roblox.deserializeModel(modelData) else modelData

	-- We'll track how long it takes for us to read the entire object tree, with all
	-- other various processes included
	local ReadStartTime = os.clock()

	local FailedCompilations = 0

	-- We'll initialize the output object tree, then walk through what we need to
	local ObjectTree = {}
	local ClosureBindings = {} -- [RefId] = Closure
	local ClosureSourceLineCounts = {} -- [RefId] = LineCount

	local ScrapedInstanceTree = {} -- [RealRef] = {Child, ...}
	local RefIds = {} -- [RefId] = RealRef

	-- Recursive function to actually walk through the real instance tree, and assign refs
	local function ScrapeInstanceChildren(instance)
		-- Add a reference id for this instance
		table.insert(RefIds, instance)

		local ScrapedChildren = {}
		for _, Child in instance:GetChildren() do
			ScrapedChildren[Child] = ScrapeInstanceChildren(Child)
		end

		return ScrapedChildren
	end

	-- Initialize the scraped instance tree and assign all refs from root
	local ModelRootChildren = ModelRoot -- Using later aswell
	for _, RealInstance in ModelRootChildren do
		ScrapedInstanceTree[RealInstance] = ScrapeInstanceChildren(RealInstance)
	end

	-- Now, we'll recursively create the fake object tree
	local function CreateObjectTree(instance, children, currentPathString: string?)
		currentPathString = currentPathString or instance.Name

		local RefId = table.find(RefIds, instance)
		local ClassName = instance.ClassName

		local ClassNameId = ClassNameIdKeys[ClassName]
		if not ClassNameId then
			Log.Warn(`Skipping instance of ClassName "{ClassName}", as it isn't supported in bundling`)
			return
		end

		local InstanceIsABaseScript = ClassName == "LocalScript" or ClassName == "Script"
		local InstanceIsAScript = InstanceIsABaseScript or ClassName == "ModuleScript"

		--[[
            {
                [1] = RefId,
                [2] = ClassName,
                [3] = Properties,
                [4] = Children?
            }
        ]]

		local InstanceObject = {
			[1] = RefId,
			[2] = ClassNameId,
		}

		-- If it's statically disabled, we just won't include the closure to run
		if InstanceIsAScript and not (InstanceIsABaseScript and instance.Disabled) then
			local ScriptSource = instance.Source

			RunIfVerbose(Log.Info, `Compile-checking {instance.ClassName} "{currentPathString}"..`)
			local CompileSuccess, CompileError = pcall(luau.compile, ScriptSource)

			if CompileSuccess then
				-- The added line here is the "\nend" below every arbitrary closure
				ClosureSourceLineCounts[RefId] = StringUtils.LineCount(ScriptSource) + 1

				-- We're using `FunctionsReturnRaw` on LuaEncode later, this will set the return
				-- to the rew value, which is the script closure
				ClosureBindings[RefId] = function()
					return "function()local wax,script,require=ImportGlobals("
						.. RefId
						.. ")local ImportGlobals return (function(...)"
						.. ScriptSource
						.. "\nend)() end"
				end
			else
				local FirstLineOfError = string.match(tostring(CompileError), "%w* ?:%d*: ?([^\n]*)\n")
				Log.Warn(
					`WARNING: {instance.ClassName} "{currentPathString}" failed to compile: {FirstLineOfError or "[Failed to parse compiler error]"}`
				)
				FailedCompilations += 1

				ClosureSourceLineCounts[RefId] = 1 -- Guaranteed 1 line; see below lol

				ClosureBindings[RefId] = function()
					return `function()error("[AOT COMPILER ERROR] {StringUtils.SerializeStringData(FirstLineOfError)}")end`
				end
			end
		end

		-- Add any properties
		local Properties = { [1] = instance.Name } -- For byte preservation (lol) the name is just set as the property index 1, and not "Name"

		if ClassName == "StringValue" then
			Properties.Value = instance.Value
		end

		-- The init script will assume the `Name` is the same as the `ClassName` if not included
		if instance.Name ~= ClassName then
			InstanceObject[3] = Properties
		end

		-- Recursively add children
		if next(children) then
			local ObjectChildren = {}

			for Child, ChildrenOfChild in children do
				local ChildObjectTree = CreateObjectTree(Child, ChildrenOfChild, `{currentPathString}.{Child.Name}`)
				if ChildObjectTree then
					table.insert(ObjectChildren, ChildObjectTree)
				end
			end

			InstanceObject[4] = ObjectChildren
		end

		return InstanceObject
	end

	for RealInstance, Children in ScrapedInstanceTree do
		local ChildObjectTree = CreateObjectTree(RealInstance, Children)

		if ChildObjectTree then
			table.insert(ObjectTree, ChildObjectTree)
		end
	end

	local LineOffsets = {}
	if LineDebugging then
		-- Where the first closure func start should be
		local CurrentLineOffset = ExtraLinesToOffset + InitialLineOffset

		for RefId, LineCount in ClosureSourceLineCounts do
			LineOffsets[RefId] = CurrentLineOffset
			CurrentLineOffset += LineCount
		end
	end

	-- Now we're done reading everything!
	local ReadEndTime = os.clock()
	RunIfVerbose(
		Log.Info,
		`Finished bundling model data in {string.format("%.4f", ReadEndTime - ReadStartTime)} (seconds)`
	)

	local Prettify = if MinifyCodegen == false then true else false

	local SerializedObjectTree = LuaEncode(ObjectTree, {
		Prettify = Prettify,
		StackLimit = math.huge,
	})

	local SerializedClosureBindings = LuaEncode(ClosureBindings, {
		Prettify = Prettify,
		FunctionsReturnRaw = true, -- For Script.Source function closures
	})

	local SerializedLineOffsets = if not LineDebugging
		then "nil"
		else LuaEncode(LineOffsets, {
			Prettify = Prettify,
		})

	local CodegenOutput = StringUtils.Replace(InitScriptTemplate, {
		Version = StringUtils.SerializeStringData(Version),
		EnvName = StringUtils.SerializeStringData(EnvName),
		ObjectTree = SerializedObjectTree,
		ClosureBindings = SerializedClosureBindings,
		LineOffsets = SerializedLineOffsets,
	})

	-- If there's a top-level init modulescript, we'll return it from the output's closure directly
	-- It's better to impl this all AoT!
	if #ModelRootChildren == 1 and ModelRootChildren[1].ClassName == "ModuleScript" then
		CodegenOutput ..= "\n-- AoT adjustment: Load init module (MainModule behavior)\nreturn LoadScript(RealObjectRoot:GetChildren()[1])"
	else
		for _, Ref in next, ModelRootChildren do
			if Ref.ClassName == "ModuleScript" and Ref.Name == "MainModule" then
				CodegenOutput ..= "\n-- AoT adjustment: Load init module (MainModule behavior)\nreturn LoadScript(RealObjectRoot.MainModule)"
				break
			end
		end
	end

	return CodegenOutput, FailedCompilations
end

return BundleModel


-- This codegen template *must* be 100% compatible with Lua 5.1x+, NOT just Luau
-- With that being said, this explains the weird cflow in some parts

return [[
-- ++++++++ WAX BUNDLED DATA BELOW ++++++++ --

-- Will be used later for getting flattened globals
local ImportGlobals

-- Holds direct closure data (defining this before the DOM tree for line debugging etc)
local ClosureBindings = ${ClosureBindings} -- [RefId] = Closure

-- Holds the actual DOM data
local ObjectTree = ${ObjectTree}

-- Line offsets for debugging (only included when minifyTables is false)
local LineOffsets = ${LineOffsets}

-- Misc AOT variable imports
local WaxVersion = "${Version}"
local EnvName = "${EnvName}"

-- ++++++++ RUNTIME IMPL BELOW ++++++++ --

-- Localizing certain libraries and built-ins for runtime efficiency
local string, task, setmetatable, error, next, table, unpack, coroutine, script, type, require, pcall, tostring, tonumber, _VERSION =
      string, task, setmetatable, error, next, table, unpack, coroutine, script, type, require, pcall, tostring, tonumber, _VERSION

local table_insert = table.insert
local table_remove = table.remove
local table_freeze = table.freeze or function(t) return t end -- lol

local coroutine_wrap = coroutine.wrap

local string_sub = string.sub
local string_match = string.match
local string_gmatch = string.gmatch

-- The Lune runtime has its own `task` impl, but it must be imported by its builtin
-- module path, "@lune/task"
if _VERSION and string_sub(_VERSION, 1, 4) == "Lune" then
    local RequireSuccess, LuneTaskLib = pcall(require, "@lune/task")
    if RequireSuccess and LuneTaskLib then
        task = LuneTaskLib
    end
end

local task_defer = task and task.defer

-- If we're not running on the Roblox engine, we won't have a `task` global
local Defer = task_defer or function(f, ...)
    coroutine_wrap(f)(...)
end

-- ClassName "IDs"
local ClassNameIdBindings = {
    [1] = "Folder",
    [2] = "ModuleScript",
    [3] = "Script",
    [4] = "LocalScript",
    [5] = "StringValue",
}

local RefBindings = {} -- [RefId] = RealObject

local ScriptClosures = {}
local ScriptClosureRefIds = {} -- [ScriptClosure] = RefId
local StoredModuleValues = {}
local ScriptsToRun = {}

-- wax.shared __index/__newindex
local SharedEnvironment = {}

-- We're creating 'fake' instance refs soley for traversal of the DOM for require() compatibility
-- It's meant to be as lazy as possible
local RefChildren = {} -- [Ref] = {ChildrenRef, ...}

-- Implemented instance methods
local InstanceMethods = {
    GetFullName = { {}, function(self)
        local Path = self.Name
        local ObjectPointer = self.Parent

        while ObjectPointer do
            Path = ObjectPointer.Name .. "." .. Path

            -- Move up the DOM (parent will be nil at the end, and this while loop will stop)
            ObjectPointer = ObjectPointer.Parent
        end

        return Path
    end},

    GetChildren = { {}, function(self)
        local ReturnArray = {}

        for Child in next, RefChildren[self] do
            table_insert(ReturnArray, Child)
        end

        return ReturnArray
    end},

    GetDescendants = { {}, function(self)
        local ReturnArray = {}

        for Child in next, RefChildren[self] do
            table_insert(ReturnArray, Child)

            for _, Descendant in next, Child:GetDescendants() do
                table_insert(ReturnArray, Descendant)
            end
        end

        return ReturnArray
    end},

    FindFirstChild = { {"string", "boolean?"}, function(self, name, recursive)
        local Children = RefChildren[self]

        for Child in next, Children do
            if Child.Name == name then
                return Child
            end
        end

        if recursive then
            for Child in next, Children do
                -- Yeah, Roblox follows this behavior- instead of searching the entire base of a
                -- ref first, the engine uses a direct recursive call
                return Child:FindFirstChild(name, true)
            end
        end
    end},

    FindFirstAncestor = { {"string"}, function(self, name)
        local RefPointer = self.Parent
        while RefPointer do
            if RefPointer.Name == name then
                return RefPointer
            end

            RefPointer = RefPointer.Parent
        end
    end},

    -- Just to implement for traversal usage
    WaitForChild = { {"string", "number?"}, function(self, name)
        return self:FindFirstChild(name)
    end},
}

-- "Proxies" to instance methods, with err checks etc
local InstanceMethodProxies = {}
for MethodName, MethodObject in next, InstanceMethods do
    local Types = MethodObject[1]
    local Method = MethodObject[2]

    local EvaluatedTypeInfo = {}
    for ArgIndex, TypeInfo in next, Types do
        local ExpectedType, IsOptional = string_match(TypeInfo, "^([^%?]+)(%??)")
        EvaluatedTypeInfo[ArgIndex] = {ExpectedType, IsOptional}
    end

    InstanceMethodProxies[MethodName] = function(self, ...)
        if not RefChildren[self] then
            error("Expected ':' not '.' calling member function " .. MethodName, 2)
        end

        local Args = {...}
        for ArgIndex, TypeInfo in next, EvaluatedTypeInfo do
            local RealArg = Args[ArgIndex]
            local RealArgType = type(RealArg)
            local ExpectedType, IsOptional = TypeInfo[1], TypeInfo[2]

            if RealArg == nil and not IsOptional then
                error("Argument " .. RealArg .. " missing or nil", 3)
            end

            if ExpectedType ~= "any" and RealArgType ~= ExpectedType and not (RealArgType == "nil" and IsOptional) then
                error("Argument " .. ArgIndex .. " expects type \"" .. ExpectedType .. "\", got \"" .. RealArgType .. "\"", 2)
            end
        end

        return Method(self, ...)
    end
end

local function CreateRef(className, name, parent)
    -- `name` and `parent` can also be set later by the init script if they're absent

    -- Extras
    local StringValue_Value

    -- Will be set to RefChildren later aswell
    local Children = setmetatable({}, {__mode = "k"})

    -- Err funcs
    local function InvalidMember(member)
        error(member .. " is not a valid (virtual) member of " .. className .. " \"" .. name .. "\"", 3)
    end
    local function ReadOnlyProperty(property)
        error("Unable to assign (virtual) property " .. property .. ". Property is read only", 3)
    end

    local Ref = {}
    local RefMetatable = {}

    RefMetatable.__metatable = false

    RefMetatable.__index = function(_, index)
        if index == "ClassName" then -- First check "properties"
            return className
        elseif index == "Name" then
            return name
        elseif index == "Parent" then
            return parent
        elseif className == "StringValue" and index == "Value" then
            -- Supporting StringValue.Value for Rojo .txt file conv
            return StringValue_Value
        else -- Lastly, check "methods"
            local InstanceMethod = InstanceMethodProxies[index]

            if InstanceMethod then
                return InstanceMethod
            end
        end

        -- Next we'll look thru child refs
        for Child in next, Children do
            if Child.Name == index then
                return Child
            end
        end

        -- At this point, no member was found; this is the same err format as Roblox
        InvalidMember(index)
    end

    RefMetatable.__newindex = function(_, index, value)
        -- __newindex is only for props fyi
        if index == "ClassName" then
            ReadOnlyProperty(index)
        elseif index == "Name" then
            name = value
        elseif index == "Parent" then
            -- We'll just ignore the process if it's trying to set itself
            if value == Ref then
                return
            end

            if parent ~= nil then
                -- Remove this ref from the CURRENT parent
                RefChildren[parent][Ref] = nil
            end

            parent = value

            if value ~= nil then
                -- And NOW we're setting the new parent
                RefChildren[value][Ref] = true
            end
        elseif className == "StringValue" and index == "Value" then
            -- Supporting StringValue.Value for Rojo .txt file conv
            StringValue_Value = value
        else
            -- Same err as __index when no member is found
            InvalidMember(index)
        end
    end

    RefMetatable.__tostring = function()
        return name
    end

    setmetatable(Ref, RefMetatable)

    RefChildren[Ref] = Children

    if parent ~= nil then
        RefChildren[parent][Ref] = true
    end

    return Ref
end

-- Create real ref DOM from object tree
local function CreateRefFromObject(object, parent)
    local RefId = object[1]
    local ClassNameId = object[2]
    local Properties = object[3] -- Optional
    local Children = object[4] -- Optional

    local ClassName = ClassNameIdBindings[ClassNameId]

    local Name = Properties and table_remove(Properties, 1) or ClassName

    local Ref = CreateRef(ClassName, Name, parent) -- 3rd arg may be nil if this is from root
    RefBindings[RefId] = Ref

    if Properties then
        for PropertyName, PropertyValue in next, Properties do
            Ref[PropertyName] = PropertyValue
        end
    end

    if Children then
        for _, ChildObject in next, Children do
            CreateRefFromObject(ChildObject, Ref)
        end
    end

    return Ref
end

local RealObjectRoot = CreateRef("Folder", "[" .. EnvName .. "]")
for _, Object in next, ObjectTree do
    CreateRefFromObject(Object, RealObjectRoot)
end

-- Now we'll set script closure refs and check if they should be ran as a BaseScript
for RefId, Closure in next, ClosureBindings do
    local Ref = RefBindings[RefId]

    ScriptClosures[Ref] = Closure
    ScriptClosureRefIds[Ref] = RefId

    local ClassName = Ref.ClassName
    if ClassName == "LocalScript" or ClassName == "Script" then
        table_insert(ScriptsToRun, Ref)
    end
end

local function LoadScript(scriptRef)
    local ScriptClassName = scriptRef.ClassName

    -- First we'll check for a cached module value (packed into a tbl)
    local StoredModuleValue = StoredModuleValues[scriptRef]
    if StoredModuleValue and ScriptClassName == "ModuleScript" then
        return unpack(StoredModuleValue)
    end

    local Closure = ScriptClosures[scriptRef]

    local function FormatError(originalErrorMessage)
        originalErrorMessage = tostring(originalErrorMessage)

        local VirtualFullName = scriptRef:GetFullName()

        -- Check for vanilla/Roblox format
        local OriginalErrorLine, BaseErrorMessage = string_match(originalErrorMessage, "[^:]+:(%d+): (.+)")

        if not OriginalErrorLine or not LineOffsets then
            return VirtualFullName .. ":*: " .. (BaseErrorMessage or originalErrorMessage)
        end

        OriginalErrorLine = tonumber(OriginalErrorLine)

        local RefId = ScriptClosureRefIds[scriptRef]
        local LineOffset = LineOffsets[RefId]

        local RealErrorLine = OriginalErrorLine - LineOffset + 1
        if RealErrorLine < 0 then
            RealErrorLine = "?"
        end

        return VirtualFullName .. ":" .. RealErrorLine .. ": " .. BaseErrorMessage
    end

    -- If it's a BaseScript, we'll just run it directly!
    if ScriptClassName == "LocalScript" or ScriptClassName == "Script" then
        local RunSuccess, ErrorMessage = pcall(Closure)
        if not RunSuccess then
            error(FormatError(ErrorMessage), 0)
        end
    else
        local PCallReturn = {pcall(Closure)}

        local RunSuccess = table_remove(PCallReturn, 1)
        if not RunSuccess then
            local ErrorMessage = table_remove(PCallReturn, 1)
            error(FormatError(ErrorMessage), 0)
        end

        StoredModuleValues[scriptRef] = PCallReturn
        return unpack(PCallReturn)
    end
end

-- We'll assign the actual func from the top of this output for flattening user globals at runtime
-- Returns (in a tuple order): wax, script, require
function ImportGlobals(refId)
    local ScriptRef = RefBindings[refId]

    local function RealCall(f, ...)
        local PCallReturn = {pcall(f, ...)}

        local CallSuccess = table_remove(PCallReturn, 1)
        if not CallSuccess then
            error(PCallReturn[1], 3)
        end

        return unpack(PCallReturn)
    end

    -- `wax.shared` index
    local WaxShared = table_freeze(setmetatable({}, {
        __index = SharedEnvironment,
        __newindex = function(_, index, value)
            SharedEnvironment[index] = value
        end,
        __len = function()
            return #SharedEnvironment
        end,
        __iter = function()
            return next, SharedEnvironment
        end,
    }))

    local Global_wax = table_freeze({
        -- From AOT variable imports
        version = WaxVersion,
        envname = EnvName,

        shared = WaxShared,

        -- "Real" globals instead of the env set ones
        script = script,
        require = require,
    })

    local Global_script = ScriptRef

    local function Global_require(module, ...)
        local ModuleArgType = type(module)

        local ErrorNonModuleScript = "Attempted to call require with a non-ModuleScript"
        local ErrorSelfRequire = "Attempted to call require with self"

        if ModuleArgType == "table" and RefChildren[module]  then
            if module.ClassName ~= "ModuleScript" then
                error(ErrorNonModuleScript, 2)
            elseif module == ScriptRef then
                error(ErrorSelfRequire, 2)
            end

            return LoadScript(module)
        elseif ModuleArgType == "string" and string_sub(module, 1, 1) ~= "@" then
            -- The control flow on this SUCKS

            if #module == 0 then
                error("Attempted to call require with empty string", 2)
            end

            local CurrentRefPointer = ScriptRef

            if string_sub(module, 1, 1) == "/" then
                CurrentRefPointer = RealObjectRoot
            elseif string_sub(module, 1, 2) == "./" then
                module = string_sub(module, 3)
            end

            local PreviousPathMatch
            for PathMatch in string_gmatch(module, "([^/]*)/?") do
                local RealIndex = PathMatch
                if PathMatch == "." then
                    RealIndex = ""
                elseif PathMatch == ".." then
                    CurrentRefPointer = CurrentRefPointer.Parent
                    RealIndex = ""
                end

                -- Don't advance dir if it's just another "/" either
                if RealIndex ~= "" then
                    local ResultRef = CurrentRefPointer:FindFirstChild(RealIndex)
                    if not ResultRef then
                        local CurrentRefParent = CurrentRefPointer.Parent
                        if CurrentRefParent then
                            ResultRef = CurrentRefParent:FindFirstChild(RealIndex)
                        end
                    end

                    if ResultRef then
                        CurrentRefPointer = ResultRef
                    elseif PathMatch ~= PreviousPathMatch and PathMatch ~= "init" and PathMatch ~= "init.server" and PathMatch ~= "init.client" then
                        error("Virtual script path \"" .. module .. "\" not found", 2)
                    end
                end

                -- For possible checks next cycle
                PreviousPathMatch = PathMatch
            end

            if CurrentRefPointer.ClassName ~= "ModuleScript" then
                error(ErrorNonModuleScript, 2)
            elseif CurrentRefPointer == ScriptRef then
                error(ErrorSelfRequire, 2)
            end

            return LoadScript(CurrentRefPointer)
        end

        return RealCall(require, module, ...)
    end

    -- Now, return flattened globals ready for direct runtime exec
    return Global_wax, Global_script, Global_require
end

for _, ScriptRef in next, ScriptsToRun do
    Defer(LoadScript, ScriptRef)
end
]]


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

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

local CommandUtils = {}

function CommandUtils.CommandExists(binary: string): boolean
	if process.os ~= "windows" then
		-- Unix-compliance is simple!
		return process.exec("type", { binary }, { shell = true }).ok -- Return from status code directly
	end

	-- I hate Windows
	return true
end

function CommandUtils.CheckCommands(requiredCommands: { string })
	-- Check required commands to continue script exec
	for _, Command in requiredCommands do
		assert(
			CommandUtils.CommandExists(Command),
			`Required command "{Command}" no found in your PATH environment variable, is it not installed?`
		)
	end
end

return CommandUtils


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

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

--[[
    This kinda looks like a strange function, because of the format a "FileTree" accepts:

    ```lua
    EnsureFileTree({
        ["test"] = {
            ["test.txt"] = "hi",
            ["test.json"] = "[\"hi again\"]",
            ["another-sub-dir"] = {
                ["bye.txt"] = "bye"
            },
        },
        ["another-test"] = {
            ["another-example.txt"] = "real",
        },
    })
    ```
]]
local function EnsureFileTree(fileTree: { [string]: any }, _currentPath: string?)
	for FileName, Entry in fileTree do
		local RealPath = if _currentPath then `{_currentPath}/{FileName}` else FileName
		local EntryType = type(Entry)

		if EntryType == "table" then -- This is to be a directory
			if not fs.isDir(RealPath) then
				fs.writeDir(RealPath)
			end

			-- Recursively continue creating the file tree..
			EnsureFileTree(Entry, RealPath)
		elseif EntryType == "string" and not fs.isFile(RealPath) then -- This is to be a file
			fs.writeFile(RealPath, Entry)
		end
	end
end

return EnsureFileTree


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

type OptionsInput = { [string]: { string | number | boolean | nil } }
type OptionsOutput = { [string]: string | number | boolean | nil }

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

local Log = require("./Log")

local function ParseArgs(args: { string }, optionsTable: OptionsInput, usageMessage: string?): OptionsOutput
	local function ErrorExit()
		if usageMessage then
			print(`\n{usageMessage}`)
		end
		process.exit(1)
	end

	local OptionsOutput = {}

	for _, Arg in args do
		local Option, Value = string.match(Arg, "([%w%-_]+)=(.+)")

		if not Option then
			Log.Error(`Command argument given, "{Arg}" not in the proper \`option=value\` format, aborting..`)
			ErrorExit()
		end

		local OptionDetails = optionsTable[Option]

		if OptionDetails == nil then -- Then it isn't a defined arg
			Log.Error(`"{Option}" isn't a valid option"`)
			ErrorExit()
		end

		local OptionType = OptionDetails[1]

		if OptionType == "boolean" then
			Value = string.lower(Value)
			Value = if Value == "true" then true elseif Value == "false" then false else nil

			if Value == nil then
				Log.Error(`"{Value}" isn't a valid boolean type; expected "true" or "false"`)
				ErrorExit()
			end
		elseif OptionType == "number" then
			Value = tonumber(Value)
			if not Value then
				Log.Error(`Failed to convert given input "{Value}" to a number value`)
				ErrorExit()
			end
		end

		OptionsOutput[Option] = Value
	end

	for Option, Details in optionsTable do
		local DefaultValue = Details[2]
		local IsRequired = Details[3] or false

		if not OptionsOutput[Option] then
			if IsRequired then
				Log.Error(`Option "{Option}" required but not provided`)
				ErrorExit()
			end

			OptionsOutput[Option] = DefaultValue
		end
	end

	return OptionsOutput
end

return ParseArgs


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

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

local function RecursiveReadDir(directory: string, _existingFileList: { string }?): { string }
	if not string.match(directory, "[/\\]$") then
		directory ..= "/"
	end

	local FileList = _existingFileList or {}
	for _, EntryName in fs.readDir(directory) do
		if fs.isFile(EntryName) then
			table.insert(FileList, directory .. EntryName)
		else -- Is dir
			RecursiveReadDir(directory .. EntryName .. "/", FileList)
		end
	end

	return FileList
end

return RecursiveReadDir


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

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

local Log = require("./Log")

local function Run(
	command: string,
	args: { string }?,
	directRun: boolean?,
	errorHandler: (string?) -> ()?
): process.ExecResult
	local Args = args or {}
	local DirectRun = if directRun == nil then false else directRun

	if not DirectRun then
		if #Args == 0 then
			Log.Info(`> {command}`)
		else
			Log.Info(`> {command} {table.concat(Args, " ")}`)
		end
	end

	local Result = process.exec(command, args, {
		shell = if process.os ~= "windows" then true else nil,
		stdio = if DirectRun then "default" else "inherit",
	})

	if not Result.ok and errorHandler then
		errorHandler(`Command above failed with status code {Result.code}`)
	end

	return Result
end

return Run


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

return {
	Log = require("./LuneUtils/Log"),
	StringUtils = require("./LuneUtils/StringUtils"),
	CommandUtils = require("./LuneUtils/CommandUtils"),
	WebhookQueue = require("./LuneUtils/WebhookQueue"),

	Run = require("./LuneUtils/Run"),
	ParseArgs = require("./LuneUtils/ParseArgs"),
	EnsureFileTree = require("./LuneUtils/EnsureFileTree"),
	RecursiveReadDir = require("./LuneUtils/RecursiveReadDir"),
}


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

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

local LuneUtils = require("./lib/libraries/LuneUtils")

local Log = LuneUtils.Log
local StringUtils = LuneUtils.StringUtils
local CommandUtils = LuneUtils.CommandUtils
local Run = LuneUtils.Run

local Version = require("./lib/data/Version")

local CodegenHeader = [[
--!nolint
--!nocheck
--!native
--!optimize 2

--[[
    Wax - A Fast Runtime-Based Lua 5.1x+/Luau Project Bundler, Using Roblox Models and Module-Require Semantics
    MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

    Wax Version: ]] .. Version .. [=[

]]

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

]=]

local BuildFlags = {
	["build-standalone-wine"] = false,
}

for _, BuildFlag in process.args do
	if BuildFlags[BuildFlag] ~= nil then
		BuildFlags[BuildFlag] = true
	end
end

local CodegenHeaderExtraLines = StringUtils.LineCount(CodegenHeader) - 1

CommandUtils.CheckCommands({ "lune", "rojo", "darklua" })

local function WriteFile(path: string, contents: string)
	Log.Info(`Writing to "{path}"..`)
	fs.writeFile(path, contents)
end

local function WriteDir(path: string)
	Log.Info(`Creating directory at "{path}"..`)
	fs.writeDir(path)
end

local function RemoveDir(path: string)
	Log.Info(`Removing directory at "{path}"..`)
	fs.removeDir(path)
end

-- Start build process

--[[
if fs.isDir("build") then
    Log.Info("Removing old `build` directory..")
    fs.removeDir("build")
end
]]

if not fs.isDir("build") then
	WriteDir("build")
end

local function HandleRun(...)
	local Result = Run(...)
	if not Result.ok then
		RemoveDir("build")
		process.exit(1)
	end
end

HandleRun("lune", {
	"run",
	"wax",
	"bundle",
	"output=build/wax.dbg.luau",
	`extra-offset-lines={CodegenHeaderExtraLines}`,
	"ci-mode=true",
})
HandleRun("lune", { "run", "wax", "bundle", "output=build/wax.luau", "minify=true", "ci-mode=true" })

Log.Info("(Adding CodegenHeader)")
WriteFile("build/wax.dbg.luau", CodegenHeader .. fs.readFile("build/wax.dbg.luau"))
WriteFile("build/wax.luau", CodegenHeader .. fs.readFile("build/wax.luau"))

print()
Log.Info(`++++++++ Finished build target ++++++++`)


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

-- This is a basic automated "test" runner, just for overlooking basic
-- functionality and making sure everything works as expected and implemented

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

local LuneUtils = require("./lib/libraries/LuneUtils")

local Log = LuneUtils.Log
local StringUtils = LuneUtils.StringUtils
local CommandUtils = LuneUtils.CommandUtils
local Run = LuneUtils.Run

CommandUtils.CheckCommands({ "lune", "luau", "lua5.1", "lua5.2", "rojo", "darklua" })

print("\n++++++++++ BUNDLING TEST PROJECTS ++++++++++\n")

local ScriptFilesToTest = {}

for _, TestName in fs.readDir("tests") do
	if not fs.isDir(`tests/{TestName}`) then
		Log.Warn(`\`tests\` entry "{TestName}" not a directory, skipping..`)
		continue
	end

	local DoNotExitForErrors = fs.isFile(`tests/{TestName}/do-not-exit-for-errors`)

	local RojoProjectFilePath = `tests/{TestName}/default.project.json`
	local OutputScriptPath = `tests/{TestName}/{TestName}.wax.lua`
	local OutputScriptPathMin = `tests/{TestName}/{TestName}.min.wax.lua`

	if not fs.isFile(RojoProjectFilePath) then
		Log.Warn(`Rojo project file for test "{TestName}" not found at "{RojoProjectFilePath}", skipping..`)
		continue
	end

	if fs.isFile(OutputScriptPath) or fs.isFile(OutputScriptPathMin) then
		Log.Info(`Existing output script at "{OutputScriptPath}", removing for re-bundling..`)

		pcall(fs.removeFile, OutputScriptPath)
		pcall(fs.removeFile, OutputScriptPathMin)
	end

	local BundleResult = Run("lune", {
		"run",
		"wax",
		"bundle",
		`input={RojoProjectFilePath}`,
		`output={OutputScriptPath}`,
		"minify=false",
		"ci-mode=true",
		"verbose=false",
	})
	local BundleResultMin = Run("lune", {
		"run",
		"wax",
		"bundle",
		`input={RojoProjectFilePath}`,
		`output={OutputScriptPathMin}`,
		"minify=true",
		"ci-mode=true",
		"verbose=false",
	})

	local BadResult = if not BundleResult.ok
		then BundleResult
		elseif not BundleResultMin.ok then BundleResultMin
		else nil
	if BadResult and not DoNotExitForErrors then
		print()
		Log.Error(`Test "{TestName}" failed to build (SEE COMMAND LOG ABOVE FOR DETAILS), aborting!`)
		process.exit(1)
	end

	table.insert(ScriptFilesToTest, { OutputScriptPath, DoNotExitForErrors })
	table.insert(ScriptFilesToTest, { OutputScriptPathMin, DoNotExitForErrors })
end

print("\n++++++++++ STARTING TEST RUNNER PROCESS ++++++++++\n")

for _, ScriptPathObject in ScriptFilesToTest do
	local ScriptPath = ScriptPathObject[1]
	local DoNotExitForErrors = ScriptPathObject[2]

	Log.Info(`Running script file "{StringUtils.FileNameFromPath(ScriptPath)}" for all targets..\n`)

	local function RunWithCommand(binary: string, args: { string }?)
		args = args or {}
		table.insert(args :: { any }, ScriptPath)

		Log.Info(`> {binary} {table.concat(args :: {}, " ")}`)
		local Result = process.spawn(binary, args, {
			shell = true,
			stdio = "inherit",
		})

		if not Result.ok and not DoNotExitForErrors then
			process.exit(1)
		end

		--[[
        Run(binary, args, false, function(message: string?)
            if not DoNotExitForErrors then
                if message then
                    Log.Error(message)
                end

                process.exit(1)
            end
        end)
        ]]
	end

	RunWithCommand("lua5.1")
	RunWithCommand("lua52")
	RunWithCommand("luau")
	RunWithCommand("lune", { "run" })

	print("--------------------------------------------------------------------------------\n")
end


-- MIT License | Copyright (c) 2023-2025 Latte Softworks <https://latte.to>

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

local BundleModel = require("./lib/BundleModel")

local FilePath = "junk/testgen.lua"

local ModelCodegen = BundleModel({})
fs.writeFile(FilePath, ModelCodegen)


error("This error is intentional, but shouldn't be in the scope of this virtual script itself", 3)


print("All descendant GetFullName() results:")
for _, Ref in next, script:GetDescendants() do
    print(Ref:GetFullName())
end

print("\nAll children of `Child` folder:")
for _, Ref in next, script.Child:GetChildren() do
    print(Ref:GetFullName())
end

print("\nFindFirstChild() result for \"AScript\" under \"AnotherChild\":")
print(script.Child.AnotherChild:FindFirstChild("AScript"):GetFullName())
print("\nShould be the same for WaitForChild():")
print(script.Child.AnotherChild:WaitForChild("AScript", math.huge):GetFullName())

print("\nFindFirstAncestor() result for \"AnotherChild2\" above \"AnotherChild2/NestedChild/AScript\":")
print(script.Child.AnotherChild2.NestedChild.AScript:FindFirstAncestor("AnotherChild2"):GetFullName())


print("wax.version: ", wax.version)
print("wax.envname: ", wax.envname)
print("wax.shared: ", wax.shared)
print("wax.script: ", wax.script)
print("wax.require: ", wax.require)


--[[
	💖 Thanks for using Satchel 💖

	Satchel is a modern open-source alternative to Roblox's default backpack 🎒

	📰 DevForum: https://devforum.roblox.com/t/2451549
	🛍 Creator Store: https://create.roblox.com/store/asset/13947506401
	🛝 Playground: https://www.roblox.com/join/bxsl5
]]

require(script.Satchel)


--!strict

--[[
	This Source Code Form is subject to the terms of the Mozilla Public
	License, v. 2.0. If a copy of the MPL was not distributed with this
	file, You can obtain one at http://mozilla.org/MPL/2.0/.
]]

--[[
	By using Satchel, you agree to provide attribution in one of the following ways:
		1. Keep the Attribution script unmodified and enabled
		2. Provide credit in your experience description by name (Satchel) and creator (@WinnersTakesAll)
		3. Have a DevForum topic linked in your experience description, crediting with the above

	For questions or exceptions to the attribution terms, reach out through the social links:
		https://create.roblox.com/store/asset/13947506401

	Thank you for supporting Satchel.
]]

local RunService = game:GetService("RunService")

if not RunService:IsStudio() then
	print("💼 Running Satchel v1.4.1 by @WinnersTakesAll")
end