--!strict

-- FIXME: This is nearly identical to Octree. Their common logic
--        COULD be consolidated, but it's not really worth doing.
--        Juggling the type annotation conflicts is not fun.

local QuadTree = {}
QuadTree.__index = QuadTree

local QuadTreeNode = {}
QuadTreeNode.__index = QuadTreeNode

type Set<T> = { [T]: true }

type Region = {
	subRegions: { Region },
	lowerBounds: Vector2,
	upperBounds: Vector2,
	position: Vector2,
	size: Vector2,

	parent: Region?,
	parentIndex: number?,
	depth: number,

	nodes: Set<QuadTreeNode>,
	nodeCount: number,
}

export type Class = typeof(setmetatable({} :: {
	_maxDepth: number,
	_nodeCount: number,
	_maxRegionSize: Vector2,
	_regionHashMap: { { Region } },
}, QuadTree))

type QuadTreeNode = typeof(setmetatable({} :: {
	_currentLowestRegion: Region?,
	_position: Vector2,

	_quadtree: Class,
	_object: any,
}, QuadTreeNode))

local EPSILON = 1e-9
local SQRT_2 = math.sqrt(2)

local SUB_REGION_POSITION_OFFSET = {
	Vector2.new(0.25, -0.25),
	Vector2.new(-0.25, -0.25),
	Vector2.new(0.25, 0.25),
	Vector2.new(-0.25, 0.25),
}

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Region Utils
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

local function createRegion(pos: Vector2, size: Vector2, parent: Region?, parentIndex: number?): Region
	return {
		subRegions = {},

		lowerBounds = pos - (size / 2),
		upperBounds = pos + (size / 2),

		position = pos,
		size = size,

		parent = parent,
		parentIndex = parentIndex,

		depth = if parent then parent.depth + 1 else 1,

		nodes = {}, -- [node] = true (contains subchild nodes too)
		nodeCount = 0,
	}
end

local function addNode(lowestSubregion: Region?, node: QuadTreeNode)
	local current = lowestSubregion
	assert(node, "Bad node")

	while current do
		if not current.nodes[node] then
			current.nodes[node] = true
			current.nodeCount += 1
		end

		current = current.parent
	end
end

local function moveNode(fromLowest: Region, toLowest: Region, node: QuadTreeNode)
	assert(fromLowest.depth == toLowest.depth, "fromLowest.depth ~= toLowest.depth")
	assert(fromLowest ~= toLowest, "fromLowest == toLowest")

	local currentFrom: Region? = fromLowest
	local currentTo: Region? = toLowest

	while currentFrom ~= currentTo do
		-- remove from current
		assert(currentFrom, "currentFrom is nil")
		assert(currentFrom.nodes[node], "Not in currentFrom")
		assert(currentFrom.nodeCount > 0, "No nodes in currentFrom")

		currentFrom.nodes[node] = nil
		currentFrom.nodeCount -= 1

		-- remove subregion!
		if currentFrom.nodeCount <= 0 and currentFrom.parentIndex then
			local parentIndex = assert(currentFrom.parentIndex)
			assert(currentFrom.parent, "Bad currentFrom.parent")
			assert(currentFrom.parent.subRegions[parentIndex] == currentFrom, "Not in subregion")
			currentFrom.parent.subRegions[parentIndex] = nil
		end

		-- add to new
		assert(currentTo, "currentTo is nil")
		assert(currentTo.nodes[node] == nil, "Failed to add")

		currentTo.nodes[node] = true
		currentTo.nodeCount += 1

		currentFrom = currentFrom.parent
		currentTo = currentTo.parent
	end
end

local function removeNode(lowestSubregion: Region?, node: QuadTreeNode)
	local current = lowestSubregion
	assert(node, "Bad node")

	while current do
		assert(current.nodes[node], "Not in current")
		assert(current.nodeCount > 0, "Current has bad node count")

		current.nodes[node] = nil
		current.nodeCount -= 1

		-- remove subregion!
		if current.nodeCount <= 0 and current.parentIndex then
			local parentIndex = assert(current.parentIndex)
			assert(current.parent, "No parent")
			assert(current.parent.subRegions[parentIndex] == current, "Not in subregion")
			current.parent.subRegions[parentIndex] = nil
		end

		current = current.parent
	end
end

local function getSearchRadiusSquared(radius: number, diameter: number, epsilon: number): number
	local diagonal = SQRT_2 * diameter
	local searchRadius = radius + diagonal
	return searchRadius * searchRadius + epsilon
end

local function getNeighborsWithinRadius(region: Region, radius: number, position: Vector2, objectsFound: { any }, nodeDistances2: { number }, maxDepth: number)
	local radiusSquared = radius * radius
	local childDiameter = region.size.X / 2
	local searchRadiusSquared = getSearchRadiusSquared(radius, childDiameter, EPSILON)

	-- for each child
	for _, childRegion in region.subRegions do
		local cposition = childRegion.position
		local offset = position - cposition
		local dist2 = offset:Dot(offset)

		-- within search radius
		if dist2 <= searchRadiusSquared then
			if childRegion.depth == maxDepth then
				for node in childRegion.nodes do
					local nodeOffset = position - node._position
					local nodeDist2 = nodeOffset:Dot(nodeOffset)

					if nodeDist2 <= radiusSquared then
						table.insert(objectsFound, node._object)
						table.insert(nodeDistances2, nodeDist2)
					end
				end
			else
				getNeighborsWithinRadius(childRegion, radius, position, objectsFound, nodeDistances2, maxDepth)
			end
		end
	end
end

local function getSubRegionIndex(region: Region, pos: Vector2): number
	local index = pos.X > region.position.X and 1 or 2

	if pos.Y > region.position.Y then
		index += 2
	end

	return index
end

local function createSubRegion(parentRegion: Region, parentIndex: number): Region
	local size = parentRegion.size
	local position = parentRegion.position
	local multiplier = SUB_REGION_POSITION_OFFSET[parentIndex]

	local p = position + (size * multiplier)
	local s = size / 2

	return createRegion(p, s, parentRegion, parentIndex)
end

local function getOrCreateSubRegionAtDepth(region: Region, position: Vector2, maxDepth: number): Region
	local current = region

	for _ = region.depth, maxDepth do
		local index = getSubRegionIndex(current, position)
		local _next = current.subRegions[index]

		-- construct
		if not _next then
			_next = createSubRegion(current, index)
			current.subRegions[index] = _next
		end

		-- iterate
		current = _next
	end

	return current
end

local function inRegionBounds(region: Region, pos: Vector2): boolean
	local lowerBounds = region.lowerBounds
	local upperBounds = region.upperBounds

	-- stylua: ignore
	return (
		pos.X >= lowerBounds.X and
		pos.X <= upperBounds.X and
		pos.Y >= lowerBounds.Y and
		pos.Y <= upperBounds.Y
	)
end

local function getTopLevelRegionHash(c: Vector2): number
	-- Normally you would modulus this to hash table size, but we want as flat of a structure as possible
	return c.X * 73856093 + c.Y * 19351301
end

local function getTopLevelRegionCellIndex(maxRegionSize: Vector2, pos: Vector2): Vector2
	-- stylua: ignore
	return Vector2.new(
		math.floor(pos.X / maxRegionSize.X + 0.5),
		math.floor(pos.Y / maxRegionSize.Y + 0.5)
	)
end

local function getOrCreateRegion(regionHashMap: { { Region } }, maxRegionSize: Vector2, pos: Vector2): Region
	local cell = getTopLevelRegionCellIndex(maxRegionSize, pos)
	local hash = getTopLevelRegionHash(cell)
	local regionList = regionHashMap[hash]

	if regionList == nil then
		regionList = {}
		regionHashMap[hash] = regionList
	end

	local regionPos = maxRegionSize * cell

	for _, region: Region in regionList do
		if region.position == regionPos then
			return region
		end
	end

	local region = createRegion(regionPos, maxRegionSize)
	table.insert(regionList, region)

	return region
end

local function getOrCreateLowestSubRegion(self: Class, pos: Vector2): Region
	local region = getOrCreateRegion(self._regionHashMap, self._maxRegionSize, pos)
	return getOrCreateSubRegionAtDepth(region, pos, self._maxDepth)
end

function QuadTreeNode.new(quadtree: Class, object: any): QuadTreeNode
	return setmetatable({
		_quadtree = assert(quadtree, "No quadtree"),
		_object = assert(object, "No object"),
		_position = Vector2.zero,
	}, QuadTreeNode)
end

function QuadTreeNode.kNearestNeighborsSearch(self: QuadTreeNode, k, radius)
	return self._quadtree:kNearestNeighborsSearch(self._position, k, radius)
end

function QuadTreeNode.GetObject(self: QuadTreeNode): any
	return self._object
end

function QuadTreeNode.RadiusSearch(self: QuadTreeNode, radius: number)
	return self._quadtree:RadiusSearch(self._position, radius)
end

function QuadTreeNode.GetPosition(self: QuadTreeNode)
	return self._position
end

function QuadTreeNode.SetPosition(self: QuadTreeNode, position: Vector2)
	if self._position == position then
		return
	end

	self._position = position

	if self._currentLowestRegion then
		if inRegionBounds(self._currentLowestRegion, position) then
			return
		end
	end

	local newLowestRegion = getOrCreateLowestSubRegion(self._quadtree, position)

	if self._currentLowestRegion then
		moveNode(self._currentLowestRegion, newLowestRegion, self)
	else
		addNode(newLowestRegion, self)
	end

	self._currentLowestRegion = newLowestRegion
end

function QuadTreeNode.Destroy(self: QuadTreeNode)
	if self._currentLowestRegion then
		removeNode(self._currentLowestRegion, self)
	end

	if self._object then
		self._object = nil
		self._quadtree._nodeCount -= 1
	end
end

function QuadTree.new(maxRegionSize: number?, maxDepth: number?): Class
	if maxRegionSize then
		assert(maxRegionSize > 0)
	end

	if maxDepth then
		assert(maxDepth > 0)
	end

	return setmetatable({
		_maxRegionSize = Vector2.one * (maxRegionSize or 64),
		_maxDepth = maxDepth or 4,

		_regionHashMap = {},
		_nodeCount = 0,
	}, QuadTree)
end

function QuadTree.IterNodes(self: Class): () -> QuadTreeNode
	return coroutine.wrap(function()
		for _, regionList in self._regionHashMap do
			for _, region in regionList do
				for node in region.nodes do
					coroutine.yield(node._position, node._object)
				end
			end
		end
	end)
end

function QuadTree.CreateNode(self: Class, pos: Vector2, obj: any): QuadTreeNode
	local object = assert(obj, "Bad object value")
	self._nodeCount += 1

	local node = QuadTreeNode.new(self, object)
	node:SetPosition(pos)

	return node
end

function QuadTree.RadiusSearch(self: Class, position: Vector2, radius: number): ({ any }, { number })
	local objectsFound = table.create(self._nodeCount)
	local nodeDistances2 = table.create(self._nodeCount)

	local diameter = self._maxRegionSize.X
	local searchRadiusSquared = getSearchRadiusSquared(radius, diameter, EPSILON)

	for _, regionList in self._regionHashMap do
		for _, region in regionList do
			local regionPos = region.position
			local offset = position - regionPos
			local dist2 = offset:Dot(offset)

			if dist2 <= searchRadiusSquared then
				getNeighborsWithinRadius(region, radius, position, objectsFound, nodeDistances2, self._maxDepth)
			end
		end
	end

	return objectsFound, nodeDistances2
end

function QuadTree.kNearestNeighborsSearch(self: Class, position: Vector2, k: number, radius: number): ({ any }, { number })
	assert(typeof(position) == "Vector2", "Bad position")
	assert(type(radius) == "number", "Bad radius")

	local objects, nodeDistances2 = self:RadiusSearch(position, radius)
	local sortable = table.create(#objects)

	for index, dist2 in pairs(nodeDistances2) do
		table.insert(sortable, {
			dist2 = dist2,
			index = index,
		})
	end

	table.sort(sortable, function(a, b)
		return a.dist2 < b.dist2
	end)

	local knearest = {}
	local knearestDist2 = {}

	for i = 1, math.min(#sortable, k) do
		local sorted = sortable[i]
		table.insert(knearestDist2, sorted.dist2)
		table.insert(knearest, objects[sorted.index])
	end

	return knearest, knearestDist2
end

return QuadTree


--!strict

local RobloxMesh = {}
RobloxMesh.__index = RobloxMesh

export type Vertex = {
	Position: Vector3,
	Normal: Vector3,
	UV: Vector2,

	Tangent: {
		Vector: Vector3,
		Sign: number,
	}?,

	Color: {
		Tint: Color3,
		Alpha: number,
	},

	Weights: {
		[string]: number,
	},
}

type Envelope = {
	Bones: { number },
	Weights: { number },
}

type Pose = {
	[string]: CFrame,
}

type Face = { number }
type LOD = { Face }

-- stylua: ignore
export type Class = typeof(setmetatable({} :: {
	LODs: { LOD },
	Bones: { Bone }?,
	Verts: { Vertex },

	Morphs: {
		[number]: {
			Label: string,
			Transforms: Pose,
		},
	}?,
}, RobloxMesh))

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

local HttpService = game:GetService("HttpService")
local Modules = script.Parent

local Bitbuf = require(Modules.Bitbuf)
local DEG2RAD = math.pi / 180

local function deepClone(t: any, instMap: { [Instance]: Instance }?): any
	local copy = table.clone(t)

	for key, value in pairs(copy) do
		if type(value) == "table" then
			copy[key] = deepClone(value, instMap)
		elseif typeof(value) == "Instance" and instMap then
			local clone = value:Clone()

			if clone then
				instMap[value] = clone
			end
		end
	end

	return copy
end

local function fromV1(file: string): Class
	local readLine = file:gmatch("[^\r\n]+")

	local header = assert(readLine())
	assert(header:sub(1, 8) == "version ", "Not a mesh file")

	local meshVersion = assert(tonumber(header:sub(9)), "Bad version header")
	assert(meshVersion >= 1 and meshVersion < 2, "mesh version not supported: " .. meshVersion)

	local numFaces = assert(tonumber(readLine()), "bad face count")
	local readXYZ = string.gmatch(assert(readLine()), "%[([^,]+),([^,]+),([^%]]+)%]")

	local xs, ys, zs
	local x, y, z

	local function nextVector3(): Vector3
		xs, ys, zs = readXYZ()

		x = tonumber(xs)
		y = tonumber(ys)
		z = tonumber(zs)

		return Vector3.new(x, y, z)
	end

	local function nextVector2(): Vector2
		xs, ys, zs = readXYZ()

		x = tonumber(xs)
		y = tonumber(ys)

		return Vector2.new(x, y)
	end

	local verts: { Vertex } = {}
	local faces: { { number } } = {}

	for i = 1, numFaces do
		for v = 1, 3 do
			local pos = nextVector3()
			local norm = nextVector3()
			local uv = nextVector2()

			if meshVersion < 1.01 then
				pos *= 0.5
			end

			table.insert(verts, {
				Position = pos,
				Normal = norm,
				UV = uv,

				Color = {
					Tint = Color3.new(1, 1, 1),
					Alpha = 1,
				},

				Weights = {},
			})
		end

		local c = i * 3
		local b = c - 1
		local a = b - 1

		local face = { a, b, c }
		table.insert(faces, face)
	end

	return setmetatable({
		LODs = { faces },
		Verts = verts,
	}, RobloxMesh)
end

function RobloxMesh.new(bin: string): Class
	local reader = Bitbuf.fromString(bin)

	local header = reader:ReadBytes(13)
	assert(header:sub(1, 8) == "version ", "Not a mesh file")

	local meshVersion = assert(tonumber(header:match("%d+")), "Bad version header")
	assert(meshVersion >= 1 and meshVersion <= 5, "mesh version not supported: " .. meshVersion)

	if meshVersion < 2 then
		return fromV1(bin)
	end

	local function skip(bytes: number)
		reader:ReadPad(bytes * 8)
	end

	local function readByte(): number
		return reader:ReadByte()
	end

	local function readBytes(count: number): { number }
		local bytes = table.create(count)

		for i = 1, count do
			bytes[i] = readByte()
		end

		return bytes
	end

	local function readFloat(): number
		return reader:ReadFloat(32)
	end

	local function readInt32(): number
		return reader:ReadInt(32)
	end

	local function readUInt16(): number
		return reader:ReadUint(16)
	end

	local function readUInt32(): number
		return reader:ReadUint(32)
	end

	local function readVector2(): Vector2
		local x = readFloat()
		local y = readFloat()

		return Vector2.new(x, y)
	end

	local function readVector3(): Vector3
		local x = readFloat()
		local y = readFloat()
		local z = readFloat()

		return Vector3.new(x, y, z)
	end

	---------------------------------------------------------------------
	-- Read Mesh
	---------------------------------------------------------------------

	local numLODs = 0
	local numVerts = 0
	local numFaces = 0
	local numBones = 0
	local numSubsets = 0

	local vertSize = 0
	local facsDataType = 0
	local boneNamesSize = 0

	-- HeaderSize
	skip(2)

	if meshVersion >= 4 then
		skip(2) -- LodType

		numVerts = readUInt32()
		numFaces = readUInt32()

		numLODs = readUInt16()
		numBones = readUInt16()

		boneNamesSize = readUInt32()
		numSubsets = readUInt16()

		skip(2) -- NumHighQualityLODs

		if meshVersion >= 5 then
			facsDataType = readUInt32()
			skip(4) -- facsDataSize
		end

		vertSize = 40
	else
		vertSize = readByte()
		skip(1) -- faceSize

		if meshVersion >= 3 then
			skip(2) -- lodOffsetSize
			numLODs = readUInt16()
		end

		numVerts = readUInt32()
		numFaces = readUInt32()
	end

	local bones: { Bone } = table.create(numBones)
	local verts: { Vertex } = table.create(numVerts)

	local faces: { { number } } = table.create(numFaces)
	local lodOffsets: { number } = table.create(numLODs)

	-- stylua: ignore
	local envelopes: { Envelope } = table.create(if numBones > 0 then numVerts else 0)

	for i = 1, numVerts do
		local vert: Vertex = {
			Weights = {},

			Color = {
				Tint = Color3.new(1, 1, 1),
				Alpha = 1,
			},

			Position = readVector3(),
			Normal = readVector3(),
			UV = readVector2(),
		}

		local xyzs = readUInt32()

		if xyzs ~= 0 then
			local tx = xyzs % 256
			local ty = bit32.rshift(xyzs, 8) % 256
			local tz = bit32.rshift(xyzs, 16) % 256
			local ts = bit32.rshift(xyzs, 24) % 256

			-- stylua: ignore
			vert.Tangent = {
				Sign = (ts - 127) / 127,

				Vector = Vector3.new(
					(tx - 127) / 127,
					(ty - 127) / 127,
					(tz - 127) / 127
				),
			}
		end

		if vertSize > 36 then
			local r = readByte()
			local g = readByte()
			local b = readByte()

			vert.Color = {
				Tint = Color3.fromRGB(r, g, b),
				Alpha = readByte() / 255,
			}
		end

		verts[i] = vert
	end

	if numBones > 0 then
		for i = 1, numVerts do
			envelopes[i] = {
				Bones = readBytes(4),
				Weights = readBytes(4),
			}
		end
	end

	-- Read Indices
	for i = 1, numFaces do
		local face = table.create(3)

		for j = 1, 3 do
			face[j] = 1 + readUInt32()
		end

		faces[i] = face
	end

	-- Read LOD offsets
	for i = 1, numLODs do
		lodOffsets[i] = readUInt32()
	end

	if numLODs < 2 or lodOffsets[2] == 0 then
		lodOffsets = { 0, numFaces }
		numLODs = 2
	end

	-- Read Bones

	for i = 1, numBones do
		local bone = Instance.new("Bone")
		local nameIndex = readInt32()
		local parentId = readUInt16()

		local _lodParentId = readUInt16()
		local _culling = readFloat()

		bone:SetAttribute("NameIndex", nameIndex)
		bone.Parent = bones[parentId + 1]
		bones[i] = bone

		local m1 = readVector3()
		local m2 = readVector3()
		local m3 = readVector3()
		local m0 = readVector3()

		-- stylua: ignore
		bone.WorldCFrame = CFrame.new(
			m0.X, m0.Y, m0.Z,
			m1.X, m1.Y, m1.Z,
			m2.X, m2.Y, m2.Z,
			m3.X, m3.Y, m3.Z
		)
	end

	-- Read Bone Names
	local boneNames = reader:ReadBytes(boneNamesSize)
	local boneMap = {}

	for i, bone in bones do
		local startAt: number = bone:GetAttribute("NameIndex") + 1
		local endAt = boneNames:find("\0", startAt)

		if endAt then
			local name = boneNames:sub(startAt, endAt - 1)
			boneMap[name] = bone
			bone.Name = name
		end
	end

	-- Read Bone Subsets
	for i = 1, numSubsets do
		local _facesBegin = readUInt32()
		local _facesLength = readUInt32()

		local vertsBegin = readUInt32()
		local vertsEnd = vertsBegin + readUInt32()

		local _numBones = readUInt32()
		local boneSubset = table.create(26, 0)

		for b = 1, 26 do
			boneSubset[b] = readUInt16()
		end

		for v = vertsBegin + 1, vertsEnd do
			local vert = verts[v]
			local envelope = envelopes[v]

			for s = 1, 4 do
				local subsetIndex = 1 + envelope.Bones[s]
				local boneId = boneSubset[subsetIndex]

				if boneId == 0xFFFF then
					continue
				end

				local bone = bones[1 + boneId]
				local weight = envelope.Weights[s]

				if weight > 0 then
					vert.Weights[bone.Name] = weight
				end
			end
		end
	end

	-- Break faces up by LOD
	local lods = table.create(numLODs - 1)

	for L = 1, numLODs - 1 do
		local lodStart = lodOffsets[L]
		local lodEnd = lodOffsets[L + 1]
		local lod = table.create(lodEnd - lodStart)

		for i = lodStart + 1, lodEnd do
			local face = faces[i]
			table.insert(lod, face)
		end

		table.insert(lods, lod)
	end

	-- Read FACS data
	local morphs = {}

	if facsDataType == 1 then
		local sizeof_faceBoneBuffer = readUInt32()
		local sizeof_faceControlBuffer = readUInt32()
		local _sizeof_quantizedTransforms = readUInt32()

		local _unknown = readUInt32()
		local numTwoPoseCorrectives = readUInt32() / 4
		local numThreePoseCorrectives = readUInt32() / 6

		local faceBoneBuffer = reader:ReadBytes(sizeof_faceBoneBuffer)
		local faceControlBuffer = reader:ReadBytes(sizeof_faceControlBuffer)

		local nameStart = 0
		local faceBoneNames = {}
		local faceControlNames = {}

		while true do
			local nameEnd = faceBoneBuffer:find("\0", nameStart)

			if nameEnd then
				local name = faceBoneBuffer:sub(nameStart, nameEnd - 1)
				table.insert(faceBoneNames, name)
				nameStart = nameEnd + 1
			else
				break
			end
		end

		nameStart = 0

		while true do
			local nameEnd = faceControlBuffer:find("\0", nameStart)

			if nameEnd then
				local name = faceControlBuffer:sub(nameStart, nameEnd - 1)
				table.insert(faceControlNames, name)
				nameStart = nameEnd + 1
			else
				break
			end
		end

		local transform = table.create(6)

		for i = 1, 6 do
			local format = readUInt16()
			local rows = readUInt32()
			local cols = readUInt32()

			local matrix = table.create(rows * cols)
			transform[i] = matrix

			if format == 1 then
				for i = 1, rows * cols do
					matrix[i] = readFloat()
				end
			elseif format == 2 then
				local min = readFloat()
				local max = readFloat()

				local range = math.abs(max - min)
				assert(range <= 65535)

				local alpha = if range > 1e-4 then range / 65535 else 0

				for i = 1, rows * cols do
					local value = readUInt16()
					matrix[i] = (value * alpha) + min
				end
			end
		end

		local numPoses = #faceControlNames + numTwoPoseCorrectives + numThreePoseCorrectives
		local poseNames = table.clone(faceControlNames)
		local poses = table.create(numPoses)

		for i = 1, numTwoPoseCorrectives do
			local poseA = faceControlNames[1 + readUInt16()]
			local poseB = faceControlNames[1 + readUInt16()]
			table.insert(poseNames, `{poseA} + {poseB}`)
		end

		for i = 1, numThreePoseCorrectives do
			local poseA = faceControlNames[1 + readUInt16()]
			local poseB = faceControlNames[1 + readUInt16()]
			local poseC = faceControlNames[1 + readUInt16()]
			table.insert(poseNames, `{poseA} + {poseB} + {poseC}`)
		end

		-- stylua: ignore
		local posTblX, posTblY, posTblZ,
		      rotTblX, rotTblY, rotTblZ = unpack(transform)

		for row, boneName in faceBoneNames do
			local begin = ((row - 1) * numPoses)

			for col = 1, numPoses do
				local pose = poses[col] or {}
				local i = begin + col

				if begin == 0 then
					poses[col] = pose
				end

				local posX = posTblX[i]
				local posY = posTblY[i]
				local posZ = posTblZ[i]

				local rotX = rotTblX[i] * DEG2RAD
				local rotY = rotTblY[i] * DEG2RAD
				local rotZ = rotTblZ[i] * DEG2RAD

				local rot = CFrame.Angles(rotX, rotY, rotZ)
				pose[boneName] = rot * CFrame.new(posX, posY, posZ)
			end
		end

		for i, name in poseNames do
			table.insert(morphs, {
				Label = name,
				Transforms = poses[i],
			})
		end
	end

	return setmetatable({
		Morphs = morphs,
		Bones = bones,
		Verts = verts,
		LODs = lods,
	}, RobloxMesh)
end

function RobloxMesh.Clone(self: Class): Class
	local instMap = {}
	local mesh = deepClone(self, instMap)

	for source, copy in instMap do
		local parent = source.Parent

		if parent and instMap[parent] then
			copy.Parent = instMap[parent]
		end
	end

	return setmetatable(mesh, RobloxMesh)
end

function RobloxMesh.Transform(self: Class, offset: CFrame?, scale: Vector3?): Class
	local copy = self:Clone()

	if scale or offset then
		for i, vert in copy.Verts do
			local pos = vert.Position
			local norm = vert.Normal

			if scale and scale ~= Vector3.one then
				pos *= scale
			end

			if offset and offset ~= CFrame.identity then
				pos = offset:PointToWorldSpace(pos)
				norm = offset:VectorToWorldSpace(norm)
			end

			vert.Position = pos
			vert.Normal = norm
		end
	end

	return copy
end

-- TODO: This doesn't merge bones yet!
--       Doing so will require some transforms.

function RobloxMesh.Append(self: Class, other: Class): Class
	local copy = self:Clone()
	local offset = #copy.Verts

	for i, vert in other.Verts do
		local newVert = deepClone(vert)
		table.insert(copy.Verts, newVert)
	end

	for lod, faces in other.LODs do
		local myFaces = copy.LODs[lod]

		if myFaces == nil then
			myFaces = {}
			copy.LODs[lod] = myFaces
		end

		for i, face in faces do
			local newFace = table.clone(face)

			for f = 1, 3 do
				newFace[f] += offset
			end

			table.insert(myFaces, newFace)
		end
	end

	return copy
end

function RobloxMesh.fromHash(hash: string): Class
	local id = 31

	for char in hash:gmatch(".") do
		local byte = char:byte()
		id = bit32.bxor(id, byte)
	end

	local url = string.format("https://c%d.rbxcdn.com/%s", id % 8, hash)
	local bin = HttpService:GetAsync(url)

	return RobloxMesh.new(bin)
end

function RobloxMesh.fromAssetId(assetId: number): Class
	local url = string.format("http://localhost:20326/asset?id=%i", assetId)
	local bin = HttpService:GetAsync(url, false)
	return RobloxMesh.new(bin)
end

function RobloxMesh.fromAsset(asset: string): Class
	local assetId = tonumber(asset:match("%d+$"))
	assert(assetId, "invalid asset")

	return RobloxMesh.fromAssetId(assetId)
end

function RobloxMesh.empty(): Class
	return setmetatable({
		LODs = { {} },
		Verts = {},
	}, RobloxMesh)
end

return RobloxMesh


-- This is a strictly typed binding of sleitnick's Signal module.
-- Use is optional but nice to have.

--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Packages = ReplicatedStorage.Packages
local Signal: any = require(Packages.Signal)

export type Connection = {
	Connected: boolean,
	Disconnect: (Connection) -> (),
	Destroy: (Connection) -> (),
}

export type Typed<Args...> = {
	Connect: (self: Typed<Args...>, func: (Args...) -> ()) -> Connection,
	ConnectOnce: (self: Typed<Args...>, func: (Args...) -> ()) -> Connection,
	Once: (self: Typed<Args...>, func: (Args...) -> ()) -> Connection,
	GetConnections: (self: Typed<Args...>) -> { Connection },
	DisconnectAll: (self: Typed<Args...>) -> (),
	Fire: (self: Typed<Args...>, Args...) -> (),
	FireDeferred: (self: Typed<Args...>, Args...) -> (),
	Wait: (self: Typed<Args...>) -> (Args...),
	Destroy: (self: Typed<Args...>) -> (),
}

export type Generic = Typed<...any>

local function bind(object: any, signal: any, func: (...any) -> ()): any
	assert(typeof(func) == "function", "bad function bind")
	assert(Signal.Is(signal) or typeof(signal) == "RBXScriptSignal", "bad signal")

	return signal:Connect(function(...)
		func(object, ...)
	end)
end

rawset(Signal, "Bind", bind)

if typeof(rawget(Signal, "Once")) ~= "function" then
	rawset(Signal, "Once", Signal.ConnectOnce)
end

-- stylua: ignore
return Signal :: {
	new: <Args...>() -> Typed<Args...>,
	Is: (object: any) -> boolean,

	Bind: (<T, Args...>(object: T, signal: RBXScriptSignal, func: (T, Args...) -> ()) -> RBXScriptConnection)
		& (<T, Args...>(object: T, signal: Typed<Args...>, func: (T, Args...) -> ()) -> Connection),

	Wrap: <Args...>(signal: RBXScriptSignal) -> Typed<Args...>
}


-- This is a strictly typed binding of sleitnick's Trove module.
-- Use is optional but nice to have.

--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Packages = ReplicatedStorage.Packages
local Modules = ReplicatedStorage.Modules

local Trove = require(Packages.Trove)
local Signal = require(Modules.Signal)

type Connection = Signal.Connection
type Signal<T...> = Signal.Typed<T...>

-- stylua: ignore
export type Class = {
	Extend: (self: Class) -> Class,
	Clone: <T>(self: Class, instance: T) -> T,

	Construct: (<T>(self: Class, createFunc: (...any) -> ...any, ...any) -> T)
	         & (<T>(self: Class, classTable: { new: ((...any) -> ...any)? }, ...any) -> T),

	Connect: (<Args...>(self: Class, signal: RBXScriptSignal, fn: (Args...) -> ()) -> RBXScriptConnection)
	       & (<Args...>(self: Class, signal: Signal<Args...>, fn: (Args...) -> ()) -> Connection),

	BindToRenderStep: (self: Class, name: string, priority: number, fn: (dt: number) -> ()) -> (),
	AttachToInstance: (self: Class, instance: Instance) -> RBXScriptConnection,
	Add: <T>(self: Class, object: T, cleanupMethod: string?) -> T,
	Remove: (self: Class, object: any) -> boolean,
	Destroy: (self: Class) -> (),
	Clean: (self: Class) -> (),
}

return Trove :: {
	new: () -> Class,
}


--!strict
local TweenService = game:GetService("TweenService")
local Modules = game.ReplicatedStorage.Modules

local CageSolver = require(Modules.CageSolver)
local RobloxMesh = require(Modules.RobloxMesh)
local CageBuilder = require(Modules.CageBuilder)

local info = TweenInfo.new(2)
local activeDummy: Model? = nil

local activeMesh = RobloxMesh.fromAssetId(11841081909)
script:SetAttribute("Username", "")

local outNode = Instance.new("Part")
outNode.CFrame = workspace.CurrentCamera.Focus
outNode.Transparency = 1
outNode.Anchored = true
outNode.CanCollide = false
outNode.CanQuery = false
outNode.CanTouch = false
outNode.Parent = workspace

type Vertex = RobloxMesh.Vertex
type RobloxMesh = RobloxMesh.Class

local liveVerts = {} :: {
	[number]: Attachment,
}

local function hashUV(uv: Vector2)
	local x = math.round(uv.X * 1e3) / 1e3
	local y = math.round(uv.Y * 1e3) / 1e3
	return math.round(x * 73856093 + y * 19351301)
end

local function makeVert(index: number)
	local vert = activeMesh.Verts[index]
	local hash = hashUV(vert.UV)

	if not liveVerts[hash] then
		local att = Instance.new("Attachment")
		att.Name = tostring(hash)
		att.Position = vert.Position
		att.Parent = outNode

		liveVerts[hash] = att
	end

	return liveVerts[hash], vert.Color.Tint
end

local function makeWire(v0: Attachment, v1: Attachment, color3: Color3)
	if not v0:FindFirstChild(v1.Name) then
		local wire = Instance.new("RodConstraint")
		wire.Visible = true
		wire.Thickness = 0.01
		wire.Attachment0 = v0
		wire.Attachment1 = v1
		wire.Name = v1.Name
		wire.Color = BrickColor.new(color3)
		wire.Parent = v0
	end
end

for i, face in activeMesh.LODs[1] do
	local a, b, c = unpack(face)

	local vertA, color = makeVert(a)
	local vertB = makeVert(b)
	local vertC = makeVert(c)

	makeWire(vertA, vertB, color)
	makeWire(vertB, vertC, color)
	makeWire(vertC, vertA, color)
end

local function switchMesh(newMesh: RobloxMesh)
	local rbf = CageSolver.new(activeMesh, newMesh)
	local marked = {}

	for inner, outer in rbf.Links do
		local hash = hashUV(inner.UV)
		local att = liveVerts[hash]

		if att then
			local tween = TweenService:Create(att, info, {
				Position = outer.Position,
			})

			marked[att] = true
			tween:Play()
		end
	end

	for vert, att in liveVerts do
		if not marked[att] then
			local tween = TweenService:Create(att, info, {
				Position = Vector3.zero,
			})

			tween:Play()
		end
	end
end

local function updateUser()
	local userName = script:GetAttribute("Username")
	local userId = game.Players:GetUserIdFromNameAsync(userName)

	local desc = game.Players:GetHumanoidDescriptionFromUserId(userId)
	local dummy = game.Players:CreateHumanoidModelFromDescription(desc, "R15")

	local primary = assert(dummy.PrimaryPart)
	primary.Anchored = true

	if activeDummy then
		activeDummy:Destroy()
	end

	local playerCage = CageBuilder.new(dummy)
	dummy:PivotTo(outNode.CFrame * CFrame.new(-5, 0, 0))
	dummy.Parent = workspace
	dummy.Name = userName
	activeDummy = dummy

	local cageMesh = playerCage:BuildSnapshot()
	switchMesh(cageMesh)
end

local listener = script:GetAttributeChangedSignal("Username")
listener:Connect(updateUser)


--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Modules = ReplicatedStorage.Modules

local CageSolver = require(Modules.CageSolver)
local RobloxMesh = require(Modules.RobloxMesh)
local CageBuilder = require(Modules.CageBuilder)

type CageSolver = CageSolver.Class
type CageMesh = "Reference" | "Cage"

type RobloxMesh = RobloxMesh.Class
type CageBuilder = CageBuilder.Class

local cageBuilders = {} :: {
	[Model]: CageBuilder,
}

local function createWireframe(part: MeshPart, cage: BaseWrap?): WireframeHandleAdornment
	warn("CreateWireframe", part, cage)

	-- stylua: ignore
	local color = Color3.new(0, 1, 1)
	local transparency = 0

	local meshId = part.MeshId
	local offset = CFrame.identity
	local target = part:FindFirstChildWhichIsA("BaseWrap")

	if cage and cage:IsA("WrapLayer") then
		meshId = cage.ReferenceMeshId
		offset = cage.ReferenceOrigin
		color = Color3.new(0, 0, 1)
	elseif target then
		meshId = target.CageMeshId
		offset = target.CageOrigin
		color = Color3.new(1, 0, 0)
		createWireframe(part, target)
	end

	local wireframe = Instance.new("WireframeHandleAdornment")
	wireframe.Transparency = transparency
	wireframe.Color3 = color
	wireframe.Adornee = part
	wireframe.Parent = part

	local success, mesh: RobloxMesh = xpcall(function()
		return RobloxMesh.fromAsset(meshId)
	end, function(err)
		warn("Error loading mesh:", meshId, "because:", err, debug.traceback())
		wireframe:Destroy()
	end)

	if success then
		local scale = part.Size / part.MeshSize
		mesh = mesh:Transform(offset, scale)

		if target then
			local cageBuilder: CageBuilder? = nil
			local rootPart = part.AssemblyRootPart or part

			if rootPart then
				local model = rootPart.Parent

				if model and model:IsA("Model") then
					local builder = cageBuilders[model]

					if not builder then
						builder = CageBuilder.new(model)
						cageBuilders[model] = builder
					end

					cageBuilder = builder
				end
			end

			if cageBuilder and target:IsA("WrapLayer") then
				local refMesh = RobloxMesh.fromAsset(target.ReferenceMeshId)
				refMesh = refMesh:Transform(target.ReferenceOrigin)

				-- Create morph for this layer.
				local solver = CageSolver.new(refMesh, mesh)

				-- Create snapshot of the target's inner cage.
				local newRefMesh = cageBuilder:BuildSnapshot()

				-- Morph the reference to the target cage.
				local weights = solver:SolveRbf()

				-- Render the morphed layer cage.
				print(weights)
			end
		end

		local verts = mesh.Verts
		local faces = mesh.LODs[1]

		for i = 1, #faces do
			local face = faces[i]
			local a, b, c = unpack(face)

			local pointA = verts[a].Position
			local pointB = verts[b].Position
			local pointC = verts[c].Position

			wireframe:AddLine(pointA, pointB)
			wireframe:AddLine(pointB, pointC)
			wireframe:AddLine(pointC, pointA)
		end
	end

	return wireframe
end

local function onDescendantAdded(part: Instance)
	if part:IsA("MeshPart") then
		local wrapLayer: WrapLayer? = part:FindFirstChildOfClass("WrapLayer")

		if wrapLayer then
			local inner = RobloxMesh.fromAsset(wrapLayer.ReferenceMeshId)
			inner = inner:Transform(wrapLayer.ReferenceOrigin)

			local outer = RobloxMesh.fromAsset(wrapLayer.CageMeshId)
			outer = outer:Transform(wrapLayer.CageOrigin)

			local wireframe = createWireframe(part)
			wireframe.Color3 = Color3.new(1, 1, 0)

			local rbf = CageSolver.new(inner, outer)
			part.Transparency = 0.9

			for innerVert, outerVert in rbf.Links do
				wireframe:AddLine(innerVert.Position, outerVert.Position)
			end
		else
			--part.LocalTransparencyModifier = 0.9
		end
	end
end

workspace.DescendantAdded:Connect(onDescendantAdded)

for i, desc in workspace:GetDescendants() do
	task.spawn(onDescendantAdded, desc)
end


--Base ScreenGui instance.
--!strict

local NexusInstance = require(script.Parent.Parent:WaitForChild("Packages"):WaitForChild("NexusInstance"))

local BaseScreenGui = {}
BaseScreenGui.ClassName = "BaseScreenGui"
BaseScreenGui.__index = BaseScreenGui

export type BaseScreenGui<T> = {
    Container: T,
    RotationOffset: CFrame,
    Depth: number,
    FieldOfView: number,
    CanvasSize: Vector2,
    Easing: number,
    PointingEnabled: boolean,
    NonReplicatedProperties: {[string]: boolean},
} & typeof(setmetatable({}, BaseScreenGui)) & T
export type NexusInstanceBaseScreenGui<T> = NexusInstance.NexusInstance<BaseScreenGui<T>>

--[[
Creates the Base ScreenGui.
--]]
function BaseScreenGui.__new<T>(self: NexusInstanceBaseScreenGui<T>, Container: T): ()
    self.Container = Container

    --Modify __index.
    local NonReplicatedProperties = {}
    self.NonReplicatedProperties = NonReplicatedProperties
    local Metatable = getmetatable(self) :: any
    local OriginalIndex = Metatable.__index
    Metatable.__index = function(self, Index: any): any
        --Return the object's value.
        local ObjectValue = OriginalIndex(self, Index)
        if ObjectValue ~= nil or NonReplicatedProperties[Index] then
            return ObjectValue
        end

        --Return the instance's value.
        return (Container :: any)[Index]
    end

    --Replicate changes.
    self:OnAnyPropertyChanged(function(PropertyName, Value)
        if NonReplicatedProperties[PropertyName] then return end
        (Container :: any)[PropertyName] = Value
    end)

    --Set the properties.
    self:DisableChangeReplication("RotationOffset")
    self.RotationOffset = CFrame.identity
    self:DisableChangeReplication("Depth")
    self.Depth = 5
    self:DisableChangeReplication("FieldOfView")
    self.FieldOfView = math.rad(50)
    if not (Container :: any):IsA("SurfaceGui") then
        self:DisableChangeReplication("CanvasSize")
    end
    self.CanvasSize = Vector2.new(1000, 1000)
    self:DisableChangeReplication("Easing")
    self.Easing = 0
    self:DisableChangeReplication("PointingEnabled")
    self.PointingEnabled = true
end

--[[
Returns if the BaseScreenGui is a type of instance.
IsA is not provided in Nexus Instance V4 and is provided for backwards compatibility.
--]]
function BaseScreenGui.IsA<T>(self: NexusInstanceBaseScreenGui<T>, ClassName: string): boolean
    warn("BaseScreenGui::IsA is deprecated.")
    return ClassName == "NexusObject" or ClassName == "NexusInstance" or ClassName == "BaseScreenGui"
end

--[[
Disables a change to replicate to the container.
--]]
function BaseScreenGui.DisableChangeReplication<T>(self: NexusInstanceBaseScreenGui<T>, Name: string)
    self.NonReplicatedProperties[Name] = true
end

--[[
Returns the container of the ScreenGui.
--]]
function BaseScreenGui.GetContainer<T>(self: NexusInstanceBaseScreenGui<T>): T
    return self.Container
end

--[[
Destroys the ScreenGui.
--]]
function BaseScreenGui.Destroy<T>(self: NexusInstanceBaseScreenGui<T>): ()
    (self.Container :: any):Destroy()
end

return NexusInstance.ToInstance(BaseScreenGui) :: NexusInstance.NexusInstanceClass<typeof(BaseScreenGui), <T>(Container: T) -> (NexusInstanceBaseScreenGui<T>)>


--Contains user interface components.
--!strict

local UserInputService = game:GetService("UserInputService")

local BaseScreenGui = require(script.Parent:WaitForChild("BaseScreenGui"))
local ScreenGui3D = require(script.Parent:WaitForChild("ScreenGui3D"))
local ScreenGui2D = require(script.Parent:WaitForChild("ScreenGui2D"))

--Return the class depending on if VR is enabled or not.
if UserInputService.VREnabled then
    return ScreenGui3D :: BaseScreenGui.BaseScreenGui<LayerCollector>
else
    return ScreenGui2D :: BaseScreenGui.BaseScreenGui<LayerCollector>
end


--Implementation of a ScreenGui for 2D players.
--!strict

local NexusInstance = require(script.Parent.Parent:WaitForChild("Packages"):WaitForChild("NexusInstance"))
local BaseScreenGui = require(script.Parent:WaitForChild("BaseScreenGui"))

local ScreenGui2D = {}
ScreenGui2D.ClassName = "ScreenGui2D"
ScreenGui2D.__index = ScreenGui2D
setmetatable(ScreenGui2D, BaseScreenGui)

export type ScreenGui2D = typeof(setmetatable({}, ScreenGui2D)) & BaseScreenGui.BaseScreenGui<ScreenGui>
export type NexusInstanceScreenGui2D = NexusInstance.NexusInstance<ScreenGui2D>

--[[
Creates a 2D ScreenGui.
--]]
function ScreenGui2D.__new(self: NexusInstanceScreenGui2D): ()
    BaseScreenGui.__new(self, Instance.new("ScreenGui"))
end

--[[
Returns if the ScreenGui2D is a type of instance.
IsA is not provided in Nexus Instance V4 and is provided for backwards compatibility.
--]]
function ScreenGui2D.IsA(self: NexusInstanceScreenGui2D, ClassName: string): boolean
    return BaseScreenGui.IsA(self, ClassName) or ClassName == "ScreenGui2D"
end

return NexusInstance.ToInstance(ScreenGui2D) :: NexusInstance.NexusInstanceClass<typeof(ScreenGui2D), () -> (NexusInstanceScreenGui2D)>


--Contains user interface components for a 3D user interface.
--!strict

local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")

local NexusInstance = require(script.Parent.Parent:WaitForChild("Packages"):WaitForChild("NexusInstance"))
local BaseScreenGui = require(script.Parent:WaitForChild("BaseScreenGui"))

local ScreenGui3D = {}
ScreenGui3D.ClassName = "ScreenGui3D"
ScreenGui3D.__index = ScreenGui3D
setmetatable(ScreenGui3D, BaseScreenGui)

export type ScreenGui3D = {
    Adornee: BasePart,
    LastRotation: CFrame,
    UpdateEvent: RBXScriptConnection?,
} & typeof(setmetatable({}, ScreenGui3D)) & BaseScreenGui.BaseScreenGui<SurfaceGui>
export type NexusInstanceScreenGui3D = NexusInstance.NexusInstance<ScreenGui3D>

--[[
Creates a 3D ScreenGui.
--]]
function ScreenGui3D.__new(self: NexusInstanceScreenGui3D): ()
    BaseScreenGui.__new(self, Instance.new("SurfaceGui"))

    --Create the Adornee.
    local NexusVRCoreContainer = Workspace.CurrentCamera:FindFirstChild("NexusVRCoreContainer")
    if not NexusVRCoreContainer then
        NexusVRCoreContainer = Instance.new("Folder")
        NexusVRCoreContainer.Name = "NexusVRCoreContainer"
        NexusVRCoreContainer.Parent = Workspace.CurrentCamera
    end
    local Adornee = Instance.new("Part")
    Adornee.Transparency = 1
    Adornee.Anchored = true
    Adornee.CanCollide = false
    Adornee.Parent = NexusVRCoreContainer
    self.Adornee = Adornee
    self.Face = Enum.NormalId.Back

    --Set the properties.
    self.AlwaysOnTop = true
    self:OnPropertyChanged("PointingEnabled", function()
        self.Adornee.CanQuery = self.Enabled and self.PointingEnabled
    end)
    self:OnPropertyChanged("Enabled", function()
        self.Adornee.CanQuery = self.Enabled and self.PointingEnabled
    end)

    --Disable replication of ScreenGui properties.
    self:DisableChangeReplication("DisplayOrder")
    self:DisableChangeReplication("IgnoreGuiInset")
    self:DisableChangeReplication("LastRotation")
    self.LastRotation = CFrame.new(Workspace.CurrentCamera:GetRenderCFrame().Position):Inverse() * Workspace.CurrentCamera:GetRenderCFrame()

    --Connect updating the size.
    self:OnPropertyChanged("Depth", function()
        self:UpdateSize()
    end)
    self:OnPropertyChanged("FieldOfView", function()
        self:UpdateSize()
    end)
    self:OnPropertyChanged("CanvasSize", function(Value)
        self:UpdateSize()
    end)

    --Update the size and position.
    self:UpdateSize()
    self:DisableChangeReplication("UpdateEvent")
    if RunService:IsClient() then --Required for Open Cloud testing.
        self.UpdateEvent = RunService.RenderStepped:Connect(function(DeltaTime: number)
            if self.Enabled then
                self:UpdateCFrame(DeltaTime)
            end
        end)
    end
end

--[[
Updates the size of the part.
--]]
function ScreenGui3D.UpdateSize(self: NexusInstanceScreenGui3D): ()
    local Width = 2 * math.tan(self.FieldOfView/2) * self.Depth
    local CanvasSize = self.CanvasSize :: Vector2
    if CanvasSize.Y <= CanvasSize.X then
        self.Adornee.Size = Vector3.new(Width, Width * (self.CanvasSize.Y / self.CanvasSize.X), 0)
    else
        self.Adornee.Size = Vector3.new(Width * (self.CanvasSize.X / self.CanvasSize.Y), Width, 0)
    end
    self.CanvasSize = self.CanvasSize
end

--[[
Updates the CFrame of the part.
--]]
function ScreenGui3D.UpdateCFrame(self: NexusInstanceScreenGui3D, DeltaTime: number): ()
    DeltaTime = DeltaTime or self.Easing

    --Update the rotation.
    local CameraCFrame = Workspace.CurrentCamera:GetRenderCFrame()
    local TargetCFrame = CFrame.new(CameraCFrame.Position):Inverse() * CameraCFrame
    if self.Easing == 0 then
        self.LastRotation = TargetCFrame
    else
        self.LastRotation = self.LastRotation:Lerp(TargetCFrame, math.clamp(DeltaTime / self.Easing, 0, 1))
    end

    --Set the CFrame.
    self.Adornee.CFrame = CFrame.new(CameraCFrame.Position) * self.LastRotation * self.RotationOffset * CFrame.new(0, 0, -self.Depth)
end

--[[
Destroys the ScreenGui.
--]]
function ScreenGui3D.Destroy(self: NexusInstanceScreenGui3D): ()
    BaseScreenGui.Destroy(self)
    if self.UpdateEvent then
        self.UpdateEvent:Disconnect()
        self.UpdateEvent = nil
    end
    self.Adornee:Destroy()
end

return NexusInstance.ToInstance(ScreenGui3D) :: NexusInstance.NexusInstanceClass<typeof(ScreenGui3D), () -> (NexusInstanceScreenGui3D)>


--Utility for parts.

local PartUtility = {}

--[[
Helper function for ray casting.
--]]
function PartUtility.RaycastToFront(AimingCFrame: CFrame, Size: Vector3, FrontCFrame: CFrame): (number, number, number)
    FrontCFrame = FrontCFrame * CFrame.new(0, 0, -Size.Z / 2)

    --Convert the aiming CFrame to a local CFrame.
	local LocalTargetCFrame = FrontCFrame:Inverse() * AimingCFrame
	local LocalTarget = LocalTargetCFrame.LookVector

	--Determine the angle away from the normal and cast the ray to the plane.
	local LookAngle = math.atan2(((LocalTarget.X ^ 2) + (LocalTarget.Y ^ 2)) ^ 0.5, LocalTarget.Z)
	local DistanceToScreen = LocalTargetCFrame.Z / math.cos(LookAngle)
	local LocalHitPosition = (LocalTargetCFrame * CFrame.new(0, 0, DistanceToScreen)).Position

	--Determine and return the relative positions.
	local RelativeX = 1 - (0.5 + (LocalHitPosition.X / Size.X))
	local RelativeY = 1 - (0.5 + (LocalHitPosition.Y / Size.Y))
    local Depth = -LocalTargetCFrame.Z * (1 / LocalTarget.Z)
	return RelativeX, RelativeY, Depth
end

--[[
Helper function for projecting.
--]]
function PartUtility.ProjectToFront(Position: Vector3, Size: Vector3, FrontCFrame: CFrame): (number, number, number)
    FrontCFrame = FrontCFrame * CFrame.new(0, 0, -Size.Z / 2)

	--Convert the aiming CFrame to a local CFrame.
	local LocalTargetCFrame = FrontCFrame:Inverse() * CFrame.new(Position)

	--Determine and return the relative positions.
	local RelativeX = 1 - (0.5 + (LocalTargetCFrame.X / Size.X))
	local RelativeY = 1 - (0.5 + (LocalTargetCFrame.Y / Size.Y))
	local Depth = -LocalTargetCFrame.Z
	return RelativeX, RelativeY, Depth
end

--[[
Ray casts to a surface. Returns the relative X and Y position
of the face, and the Z for the direction (>0 is facing, <0
is not facing).
--]]
function PartUtility.Raycast(Part: BasePart, AimingCFrame: CFrame, Face: Enum.NormalId | string): (number, number, number)
    local Size = Part.Size
    if Face == Enum.NormalId.Front or Face == "Front" then
        return PartUtility.RaycastToFront(AimingCFrame, Size, Part.CFrame)
    elseif Face == Enum.NormalId.Back or Face == "Back" then
        return PartUtility.RaycastToFront(AimingCFrame, Size, Part.CFrame * CFrame.Angles(0, math.pi, 0))
    elseif Face == Enum.NormalId.Top or Face == "Top" then
        local RelativeX, RelativeY, Depth = PartUtility.RaycastToFront(AimingCFrame, Vector3.new(Size.X, Size.Z, Size.Y), Part.CFrame * CFrame.Angles(math.pi / 2, 0, 0))
        return 1 - RelativeX, RelativeY, Depth
    elseif Face == Enum.NormalId.Bottom or Face == "Bottom" then
        local RelativeX, RelativeY, Depth = PartUtility.RaycastToFront(AimingCFrame, Vector3.new(Size.X, Size.Z, Size.Y), Part.CFrame * CFrame.Angles(-math.pi / 2, 0, 0))
        return RelativeX, 1 - RelativeY, Depth
    elseif Face == Enum.NormalId.Left or Face == "Left" then
        return PartUtility.RaycastToFront(AimingCFrame, Vector3.new(Size.Z, Size.Y, Size.X), Part.CFrame * CFrame.Angles(0, math.pi / 2, 0))
    elseif Face == Enum.NormalId.Right or Face == "Right" then
        return PartUtility.RaycastToFront(AimingCFrame, Vector3.new(Size.Z, Size.Y, Size.X), Part.CFrame * CFrame.Angles(0, -math.pi / 2, 0))
    end
    error("Unknown face type: "..tostring(Face))
end

--[[
Returns the relative position that is projected onto the
plane. Returns the relative X and Y position of the face,
and the Z for the direction (>0 is before the plane, <0
is after the plane).
--]]
function PartUtility.Project(Part: BasePart, HandPosition: Vector3, Face: Enum.NormalId | string): (number, number, number)
    local Size = Part.Size
    if Face == Enum.NormalId.Front or Face == "Front" then
        return PartUtility.ProjectToFront(HandPosition, Size, Part.CFrame)
    elseif Face == Enum.NormalId.Back or Face == "Back" then
        return PartUtility.ProjectToFront(HandPosition, Size, Part.CFrame * CFrame.Angles(0, math.pi, 0))
    elseif Face == Enum.NormalId.Top or Face == "Top" then
        local RelativeX, RelativeY, Depth = PartUtility.ProjectToFront(HandPosition, Vector3.new(Size.X, Size.Z, Size.Y), Part.CFrame * CFrame.Angles(math.pi / 2, 0, 0))
        return 1 - RelativeX, RelativeY, Depth
    elseif Face == Enum.NormalId.Bottom or Face == "Bottom" then
        local RelativeX, RelativeY, Depth = PartUtility.ProjectToFront(HandPosition, Vector3.new(Size.X, Size.Z, Size.Y), Part.CFrame * CFrame.Angles(-math.pi / 2, 0, 0))
        return RelativeX, 1 - RelativeY, Depth
    elseif Face == Enum.NormalId.Left or Face == "Left" then
        return PartUtility.ProjectToFront(HandPosition, Vector3.new(Size.Z, Size.Y, Size.X), Part.CFrame * CFrame.Angles(0, math.pi / 2, 0))
    elseif Face == Enum.NormalId.Right or Face == "Right" then
        return PartUtility.ProjectToFront(HandPosition, Vector3.new(Size.Z, Size.Y, Size.X), Part.CFrame * CFrame.Angles(0, -math.pi / 2, 0))
    end
    error("Unknown face type: "..tostring(Face))
end

return PartUtility


--Main module for Nexus VR Core.
--!strict

local BaseScreenGui = require(script:WaitForChild("Container"):WaitForChild("BaseScreenGui"))
local ScreenGui = require(script:WaitForChild("Container"):WaitForChild("ScreenGui"))
local ScreenGui2D = require(script:WaitForChild("Container"):WaitForChild("ScreenGui2D"))
local ScreenGui3D = require(script:WaitForChild("Container"):WaitForChild("ScreenGui3D"))
local PartUtility = require(script:WaitForChild("Utility"):WaitForChild("PartUtility"))

local NexusVRCore = {}
NexusVRCore.BaseScreenGui = BaseScreenGui
NexusVRCore.ScreenGui = ScreenGui
NexusVRCore.ScreenGui2D = ScreenGui2D
NexusVRCore.ScreenGui3D = ScreenGui3D
NexusVRCore.PartUtility = PartUtility

export type BaseScreenGui<T> = BaseScreenGui.BaseScreenGui<T>
export type NexusInstanceBaseScreenGui<T> = BaseScreenGui.NexusInstanceBaseScreenGui<T>
export type ScreenGui2D = ScreenGui2D.ScreenGui2D
export type NexusInstanceScreenGui2D = ScreenGui2D.NexusInstanceScreenGui2D
export type ScreenGui3D = ScreenGui3D.ScreenGui3D
export type NexusInstanceScreenGui3D = ScreenGui3D.NexusInstanceScreenGui3D

--[[
Returns a resource in Nexus VR Core.
Legacy from Nexus Project.
--]]
function NexusVRCore:GetResource(Path: string): any
    local Module = script
    for _, PathPart in string.split(Path, ".") do
        Module = (Module :: any)[PathPart]
    end
    return require(Module :: ModuleScript) :: any
end

return NexusVRCore


-- Author: iGottic

local OnAttached = {}

-- Types
export type OnAttached = (Object : any, Callback : (AttachedInstance : Instance) -> nil) -> RBXScriptConnection

function OnAttached:__call(Object : any, Callback : (AttachedInstance : Instance) -> nil)
    return (Object.AttachedToInstance :: RBXScriptSignal):Connect(function(...)
        -- When something changes, call the callback function
        Callback(...)
    end)
end

function OnAttached:__index(Index : string)
    if Index == "__SEAM_INDEX" then
        return "OnAttached"
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, OnAttached)

return Meta :: OnAttached


-- Author: iGottic

local OnAttributeChanged = {}

-- Types
export type OnAttributeChanged = (AttributeName : string) -> (...any) -> RBXScriptConnection

function OnAttributeChanged:__call(AttributeName : string)
    local ActiveConnection : RBXScriptConnection = nil

    local EventBinding = setmetatable({
        Destroy = function()
            if ActiveConnection and ActiveConnection.Connected then
                ActiveConnection:Disconnect()
            end
        end,
    }, {
        __call = function(_, Object : Instance, Callback : (...any?) -> nil)
            if ActiveConnection then
                return
            end

            ActiveConnection = Object:GetAttributeChangedSignal(AttributeName):Connect(Callback)

            return ActiveConnection
        end,

        __index = function(_, Index : string)
            if Index == "__SEAM_INDEX" then
                return "OnAttributeChanged"
            else
                return nil
            end
        end
    })

    return EventBinding
end

function OnAttributeChanged:__index(Index : string)
    if Index == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, OnAttributeChanged)

return Meta :: OnAttributeChanged


-- Author: iGottic

local OnChanged = {}

-- Types
export type OnChanged = (Object : any, Callback : (PropertyChanged : string, NewValue : any) -> nil) -> RBXScriptConnection

function OnChanged:__call(Object : any, Callback : (PropertyChanged : string, NewValue : any) -> nil)
    -- Fortunately, states also have a Changed signal, so this works out perfectly
    -- for states as well!

    return (Object.Changed :: RBXScriptSignal):Connect(function(PropertyName : string)
        -- When something changes, call the callback function
        Callback(PropertyName, Object[PropertyName])
    end)
end

function OnChanged:__index(Index : string)
    if Index == "__SEAM_INDEX" then
        return "OnChanged"
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, OnChanged)

return Meta :: OnChanged


-- Author: iGottic

local OnEvent = {}

-- Types
export type OnEvent = (EventName : string) -> (...any) -> RBXScriptConnection

function OnEvent:__call(EventName : string)
    local ActiveConnection : RBXScriptConnection = nil

    local EventBinding = setmetatable({
        Destroy = function()
            if ActiveConnection and ActiveConnection.Connected then
                ActiveConnection:Disconnect()
            end
        end,
    }, {
        __call = function(_, Object : Instance, Callback : (...any?) -> nil)
            -- So for example, if we do OnEvent "Activated", EventName will equal "Activated"

            if ActiveConnection then
                return
            end

            ActiveConnection = (Object[EventName] :: RBXScriptSignal):Connect(Callback)

            return ActiveConnection
        end,

        __index = function(_, Index : string)
            if Index == "__SEAM_INDEX" then
                return "OnEvent"
            else
                return nil
            end
        end
    })

    return EventBinding
end

function OnEvent:__index(Index : string)
    if Index == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, OnEvent)

return Meta :: OnEvent


-- Author: iGottic

local Attribute = {}

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Types = require(Modules.Types)
local Symbol = require(Modules.Symbol)

-- Variable
local ClassSymbol = Symbol.new("Attribute")

-- Types Extended
export type Attribute = (AttributeName : string) -> (Object : Instance, AttributeValue : Types.BaseState<any> | any) -> nil

function Attribute:__call(AttributeName : string)
    local EventBinding = setmetatable({}, {
        __call = function(_, Object : Instance, AttributeValue : any)
            if typeof(AttributeValue) == "table" and AttributeValue.__SEAM_OBJECT then
                -- If we are using a state, then let's connect to it
                -- When it changes, update the attribute

                AttributeValue.Changed:Connect(function()
                    Object:SetAttribute(AttributeName, AttributeValue.Value)
                end)

                Object:SetAttribute(AttributeName, AttributeValue.Value)

                return
            end

            -- If we're not using a state, just set the attribute directly
            Object:SetAttribute(AttributeName, AttributeValue)
        end,

        __index = function(_, Index : string)
            if Index == "__SEAM_INDEX" then
                return ClassSymbol
            else
                return nil
            end
        end
    })

    return EventBinding
end

function Attribute:__index(Index : string)
    if Index == "__SEAM_CAN_BE_SCOPED" then
        -- This cannot be scoped
        return false
    else
        return nil
    end
end

local Meta = setmetatable({}, Attribute)

return Meta :: Attribute


-- Author: iGottic

local Children = {}

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Types = require(Modules.Types)
local Symbol = require(Modules.Symbol)

-- Variables
local ClassSymbol = Symbol.new("Children")

-- Types Extended
export type Children = (Object : Instance, Children : {Types.Child}) -> nil

local function ApplyChildren(Parent : Instance, Children : {Types.Child} | Types.Child)
    -- This function just goes through the table and sets the parent of
    -- every thing in there to the determined parent.

    local ChildrenCreated = {}

    if typeof(Children) ~= "table" then
        if typeof(Children) == "Instance" then
            Children = {Children}
        else
            error("Invalid children type! Expected table or instance, got " .. typeof(Children))
        end
    end

    for _, Child in Children do
        if typeof(Child) ~= "Instance" then
            error("Invalid child type! Expected Instance, got " .. typeof(Child))
        end

        local Success = pcall(function()
            Child.Parent = Parent
        end)

        if Success then
            table.insert(ChildrenCreated, Child)
        end
    end

    return ChildrenCreated
end

local function GetOldChildren(OldChildren : {Instance}, NewChildren : {Instance})
    local DifferenceSet = {}

    for _, OldChild in OldChildren do
        if not table.find(NewChildren, OldChild) then
            table.insert(DifferenceSet, OldChild)
            continue
        end
    end

    return DifferenceSet
end

function Children:__call(Object : Instance, Children : Types.BaseState<any> | {any})
    if typeof(Children) == "table" then
        if Children.__SEAM_OBJECT and tostring(Children.__SEAM_OBJECT) == "ComputedInstance" then -- Since 0.1.0, you can use computed as the children
            local ActiveChildren = ApplyChildren(Object, Children.Value)

            local Connection = Children.Changed:Connect(function()
                -- When computed updates, update the children

                local DifferenceSet = GetOldChildren(ActiveChildren, Children.Value)

                for _, Child in DifferenceSet do
                    Child:Destroy()
                end

                ActiveChildren = ApplyChildren(Object, Children.Value)
            end)

            -- Also make sure we clean things up when the parent is destroyed
            Object.Destroying:Connect(function()
                Connection:Disconnect()

                for _, Child in ActiveChildren do
                    Child:Destroy()
                end
            end)

            return
        end
    end

    -- If it's not a computed instance, just apply the array of children to the parent
    ApplyChildren(Object, Children)
end

function Children:__index(Index : string)
    if Index == "__SEAM_INDEX" then
        return ClassSymbol
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return false
    else
        return nil
    end
end

local Meta = setmetatable({}, Children)

return Meta :: Children


-- Author: iGottic

-- Imports
local Scope = require(script.Parent.Parent.Memory.Scope)
local Symbol = require(script.Parent.Parent.Parent.Modules.Symbol)

-- Types extended
export type Component = ({[any] : any}) -> (Scope.ScopeInstance?, {[string] : any}) -> any

local function Component(ComponentModule : {[any] : any})
    -- This function just validates what we have and turns the module into a class

    if not ComponentModule.Init then
        error("Component requires an Init() method")
    end

    if not ComponentModule.Construct then
        error("Component requires a Construct() method")
    end

    ComponentModule.__index = ComponentModule

    -- ThisScope : Scope.ScopeInstance?, Properties : {[string] : any}

    local ComponentSymbol = Symbol.new("Component")

    local Meta = setmetatable({}, {
        __call = function(_, ThisScope : Scope.ScopeInstance?, Properties : {[string] : any}) : any
            local ClassInstance = setmetatable({}, ComponentModule) -- OOP but only kinda

            ClassInstance:Init(ThisScope, Properties)

            return ClassInstance:Construct(ThisScope, Properties)
        end,

        __index = function(_, Index : string)
            if Index == "__SEAM_COMPONENT" then
                return ComponentSymbol
            end

            return ComponentModule[Index]
        end,

        __newindex = function(_, Index : string, Value : any?)
            ComponentModule[Index] = Value
        end
    })

    return Meta :: (ThisScope : Scope.ScopeInstance?, Properties : {[string] : any}) -> any
end

return Component :: Component


-- Author: iGottic

local Destroyed = {}

-- Types
export type Destroyed = (Object : Instance, Callback : () -> nil) -> nil

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Symbol = require(Modules.Symbol)

-- Variables
local ClassSymbol = Symbol.new("Destroyed")

function Destroyed:__call(Object : Instance, Callback : () -> nil)
    Object.Destroying:Once(Callback)
end

function Destroyed:__index(Index : string)
    if Index == "__SEAM_INDEX" then
        return ClassSymbol
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return false
    else
        return nil
    end
end

local Meta = setmetatable({}, Destroyed)

return Meta :: Destroyed


-- Author: iGottic

local EventSequence = {}

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Symbol = require(Modules.Symbol)
local Trove = require(Modules.Trove)
local UpdateSignals = require(Modules.UpdateSignals)

-- Types extended
export type SequenceKeyframe = {number | () -> nil}

export type EventSequenceInstance = {
    Play : (EventSequenceInstance) -> nil,
    Stop : (EventSequenceInstance) -> nil,
    Pause : (EventSequenceInstance) -> nil,
    Resume : (EventSequenceInstance) -> nil,
    Looped : boolean,
    LoopDelayTime : number,
}

export type EventSequence = ({SequenceKeyframe}) -> EventSequenceInstance

-- Variables
local ClassSymbol = Symbol.new("EventSequence")

function EventSequence:__call(SequenceKeyframes : {SequenceKeyframe}) : EventSequenceInstance
    local CurrentTime = 0
    local PlayedIndexes = {}
    local EventSequenceInstance : EventSequenceInstance = {}
    local PlayingTrove = nil
    local CurrentlyPaused = false

    for Index, Keyframe in SequenceKeyframes do
        if typeof(Index) ~= "number" then
            error("Attmped to pass dictionary for sequence keyframes")
        end

        if typeof(Keyframe[1]) ~= "number" then
            error("EventSequenceKeypoint needs a time")
        end

        if typeof(Keyframe[2]) ~= "function" then
            error("EventSequenceKeypoint needs callback")
        end
    end

    EventSequenceInstance.Looped = false
    EventSequenceInstance.LoopDelayTime = 0

    function EventSequenceInstance.Play()
        if PlayingTrove then
            EventSequenceInstance.Stop()
        end

        CurrentlyPaused = false
        PlayingTrove = Trove.new()

        PlayingTrove:Add(UpdateSignals.OnFrameUpdate:Connect(function(Delta : number)
            if CurrentlyPaused then
                return
            end

            CurrentTime += Delta

            for Index, Keyframe in SequenceKeyframes do
                if PlayedIndexes[Index] then
                    continue
                end

                if CurrentTime >= Keyframe[1] then
                    PlayedIndexes[Index] = true
                    task.spawn(Keyframe[2])
                end
            end

            if #SequenceKeyframes == #PlayedIndexes then
                if EventSequenceInstance.Looped then
                    CurrentTime = -EventSequenceInstance.LoopDelayTime
                    table.clear(PlayedIndexes)
                else
                    PlayingTrove:Destroy()
                end
            end
        end))

        PlayingTrove:Add(function()
            CurrentTime = 0
            PlayingTrove = nil
            CurrentlyPaused = false
            PlayedIndexes = {}
        end)
    end

    function EventSequenceInstance.Stop()
        if not PlayingTrove then
            return
        end

        PlayingTrove:Destroy()
    end

    function EventSequenceInstance.Pause()
        if not PlayingTrove then
            return
        end

        CurrentlyPaused = true
    end

    function EventSequenceInstance.Resume()
        if not PlayingTrove then
            return
        end

        CurrentlyPaused = false
    end

    return EventSequenceInstance
end

function EventSequence:__index(Index : string)
    if Index == "__SEAM_OBJECT" then
        return ClassSymbol
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, EventSequence)

return Meta :: EventSequence


-- Author: iGottic

local FollowAttribute = {}

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Types = require(Modules.Types)
local Symbol = require(Modules.Symbol)

-- Variables
local ClassSymbol = Symbol.new("FollowAttribute")

-- Types Extended
export type FollowAttribute = (AttributeName : string) -> (Object : Instance, ValueState : Types.BaseState<any>) -> nil

function FollowAttribute:__call(AttributeName : string)
    return setmetatable({}, {
        __call = function(_, Object : Instance, ValueState : Types.BaseState<any>)
            -- Literally just a simple connection and value update

            Object:GetAttributeChangedSignal(AttributeName):Connect(function()
                if Object:GetAttribute(AttributeName) ~= ValueState.Value then
                    ValueState.Value = Object:GetAttribute(AttributeName)
                end

                ValueState.Value = Object:GetAttribute(AttributeName)
            end)
        end,

        __index = function(_, Index : string)
            if Index == "__SEAM_INDEX" then
                return ClassSymbol
            elseif Index == "__SEAM_CAN_BE_SCOPED" then
                return false
            else
                return nil
            end
        end,
    })
end

local Meta = setmetatable({}, FollowAttribute)

return Meta :: FollowAttribute


-- Author: iGottic

local FollowProperty = {}

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Types = require(Modules.Types)
local Symbol = require(Modules.Symbol)

-- Variables
local ClassSymbol = Symbol.new("FollowProperty")

-- Types Extended
export type FollowProperty = (PropertyName : string) -> (Object : Instance, ValueState : Types.BaseState<any>) -> nil

function FollowProperty:__call(PropertyName : string)
    return setmetatable({}, {
        __call = function(_, Object : Instance, ValueState : Types.BaseState<any>)
            -- Literally just a simple connection and value update

            Object:GetPropertyChangedSignal(PropertyName):Connect(function()
                if Object[PropertyName] ~= ValueState.Value then
                    ValueState.Value = Object[PropertyName]
                end

                ValueState.Value = Object[PropertyName]
            end)
        end,

        __index = function(_, Index : string)
            if Index == "__SEAM_INDEX" then
                return ClassSymbol
            elseif Index == "__SEAM_CAN_BE_SCOPED" then
                return false
            else
                return nil
            end
        end,
    })
end

local Meta = setmetatable({}, FollowProperty)

return Meta :: FollowProperty


-- Author: iGottic

local Lifetime = {}

-- Types
export type Lifetime = (Object : Instance, CleanupTime : number) -> nil

-- Services
local DebrisService = game:GetService("Debris")

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Symbol = require(Modules.Symbol)

-- Variables
local ClassSymbol = Symbol.new("Lifetime")

function Lifetime:__call(Object : Instance, CleanupTime : number)
    -- This is really simple, but the reason I use Debris service
    -- over a task.delay() is because it guarantees error-free cleanup

    DebrisService:AddItem(Object, CleanupTime)
end

function Lifetime:__index(Index : string)
    if Index == "__SEAM_INDEX" then
        return ClassSymbol
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return false
    else
        return nil
    end
end

local Meta = setmetatable({}, Lifetime)

return Meta :: Lifetime


-- Author: iGottic

local New = {}

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Symbol = require(Modules.Symbol)
local CreateDeepTraceback = require(Modules.CreateDeepTraceback)
local Scope = require(script.Parent.Parent.Memory.Scope)
local Children = require(script.Parent.Children)

-- Types extended
export type NewConstructor = (Class : string | Instance | (Scope.ScopeInstance?, {[any] : any}) -> any, Properties : {[any] : any}) -> Instance

-- Variables
local ClassSymbol = Symbol.new("New")

function New:__call(Class : string | Instance | (Scope.ScopeInstance?, {[any] : any}) -> any, Properties : {[any] : any}, ThisScope : Scope.ScopeInstance?)
    local Object = nil

    if typeof(Class) == "Instance" then
        -- Hydration
        Object = Class
    elseif typeof(Class) == "string" then
        -- Construction
        Object = Instance.new(Class)
    elseif typeof(Class) == "table" and Class.__SEAM_COMPONENT then
        -- Custom component
        Object = nil
    else
        -- Invalid class
        error(`Invalid class type! Expected string or instance, got {typeof(Class)}: {CreateDeepTraceback()}`)
    end

    if Object then
        -- Normal construction or hydration
        for Index, Property in Properties do
            if typeof(Index) == "table" then
                if Index.__SEAM_INDEX then
                    -- A seam index is something like Attribute, Children, or Lifetime
                    Index(Object, Property)
                else
                    error(`Object hydration recieved invalid index type! Expected Seam object or string, got table ({Object:GetFullName()}); {CreateDeepTraceback()}`)
                end
            elseif typeof(Index) == "number" then -- Allows for implicit
                Children(Object, Property)
            elseif typeof(Property) == "table" then
                if Property.__SEAM_OBJECT then
                    -- A seam object is something like Computed, Value, or Spring
                    Property(Object, Index)
                else
                    error(`Invalid property type! Expected Seam object or string, got table: {CreateDeepTraceback()}`)
                end
            else
                Object[Index] = Property
            end
        end
    else
        -- Custom component
        Object = Class(ThisScope, Properties)
    end

    return Object
end

function New:__index(Index : string)
    if Index == "__SEAM_OBJECT" then
        return ClassSymbol
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, New)

return Meta :: NewConstructor


-- Author: iGottic

local Tags = {}

-- Types
export type Tags = (Object : Instance, AddedTags : {string}) -> nil

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local Symbol = require(Modules.Symbol)

-- Variables
local ClassSymbol = Symbol.new("Tags")

function Tags:__call(Object : Instance, AddedTags : {string})
    for _, Tag in AddedTags do -- Super simple iteration through strings
        Object:AddTag(Tag)
    end
end

function Tags:__index(Index : string)
    if Index == "__SEAM_INDEX" then
        return ClassSymbol
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return false
    else
        return nil
    end
end

local Meta = setmetatable({}, Tags)

return Meta :: Tags


-- Author: iGottic

local Scope = {}

-- Imports
local Constructors = script.Parent.Parent
local Modules = Constructors.Parent.Modules
local States = Constructors.States
local Trove = require(Modules.Trove)
local Types = require(Modules.Types)
local Symbol = require(Modules.Symbol)
local CreateDeepTraceback = require(Modules.CreateDeepTraceback)
local Computed = require(States.Computed)
local Rendered = require(States.Rendered)
local Value = require(States.Value)
local Spring = require(States.Animation.Spring)
local Tween = require(States.Animation.Tween)
local ForPairs = require(States.ForPairs)

-- Variables
local ClassSymbol = Symbol.new("Scope")

-- Extended types
export type ScopeInstance = {
    New : (self : ScopeInstance, Class : string | Instance | {[string] : any?}, Properties : {[any] : any}, From : any?) -> Instance,
    Rendered : (self : ScopeInstance, Callback : () -> any?) -> Rendered.RenderedInstance<any>,
    Value : (self : ScopeInstance, InitialValue : any) -> Value.ValueInstance<any>,
    Spring : (self : ScopeInstance, Value : Types.BaseState<any> | any, Speed : number, Dampening : number) -> Spring.SpringInstance<any>,
    Tween : (self : ScopeInstance, Value : Types.BaseState<any> | any, TweenInformation : TweenInfo) -> Tween.TweenInstance<any>,
    Computed : (self : ScopeInstance, Callback : ((Value : Value.ValueInstance<any>) -> any) -> any?) -> Computed.ComputedInstance<any>,
    ForPairs : (self : ScopeInstance, TrackedValue : Value.ValueInstance<any>, Callback : ((Value : Value.ValueInstance<any>) -> any) -> any?) -> ForPairs.ForPairsInstance<any>,
    OnChanged : (self : ScopeInstance, Object : any, Callback : (PropertyChanged : string, NewValue : any) -> nil) -> RBXScriptConnection,
    OnAttributeChanged : (self : ScopeInstance, AttributeName : string) -> (...any) -> RBXScriptConnection,
    OnEvent : (self : ScopeInstance, EventName : string) -> (...any) -> RBXScriptConnection,
    OnAttached : (self : ScopeInstance, Object : any, Callback : (AttachedInstance : Instance) -> nil) -> RBXScriptConnection,

    InnerScope : (self : ScopeInstance, ScopedObjects : {[string] : any}?) -> ScopeInstance,
    AddObject : (self : ScopeInstance, Object : any) -> (),
    RemoveObject : (self : ScopeInstance, Object : any) -> (),
    Destroy : (self : ScopeInstance) -> (),
}

export type ScopeConstructor = (ScopedObjects : {[string] : (...any) -> ...any}) -> ScopeInstance

local Meta = setmetatable({}, Scope)

function Scope.__call(_, ScopedObjects : {[string] : any})
    -- Scopes have been a headache to get working, since I'm a dumdum

    local selfClass = {}
    local selfMeta = {}
    local InstanceSymbol = Symbol.new("Scope")

    if not ScopedObjects then
        ScopedObjects = {}
    end

    ScopedObjects = table.clone(ScopedObjects)

    function selfMeta:__index(Key : string)
        if not Key then
            warn(`Attempt to index a scope with nil{"\n"}{CreateDeepTraceback()}`)
            return
        end

        if Key == "__SEAM_OBJECT" then
            -- Some things check if something is a seam object, so
            -- this is the first thing we should try to return
            return InstanceSymbol
        end

        local Object = ScopedObjects[Key]

        if Object == nil then
            warn(`Attempt to index a scope with {Key} when it does not exist in scope{"\n"}{CreateDeepTraceback()}`)
            return nil
        end

        if typeof(Object) ~= "function" and (typeof(Object) ~= "table" or not Object.__SEAM_CAN_BE_SCOPED) then
            if Object.__SEAM_OBJECT or Object.__SEAM_INDEX then
                -- If something from seam has __SEAM_CAN_BE_SCOPED (meaning it can't be scoped) as false,
                -- then we should error what specifically the user tried to use
                error(tostring(Object.__SEAM_OBJECT or Object.__SEAM_INDEX) .. " is not a valid scopable Seam object")
            else
                -- Idk just error
                error("Object is not a valid scopable Seam object")
            end
        end

        return function(_, ...)
            if not self.Trove then -- If the trove instance doesn't exist, the scope is likely destroyed
                warn(`Attempted to use something in scope ({Key}) but scope is already destroyed: {CreateDeepTraceback()}`)
                return
            end

            -- Seam things are called as functions, so this is a wrapper
            -- function that puts any created instances into the trove
            local Tuple = nil

            if typeof(Object) == "function" then
                -- If it's a non-Seam function, let's pass the scope as the first parameter,
                -- then pass in everything else
                Tuple = {Object(self, ...)}
            elseif Object.__SEAM_OBJECT and tostring(Object.__SEAM_OBJECT) == "New" then
                -- For New specifically, we want to actually put scope at the end. In the
                -- future, if seam requires scopes, this will change
                local Args = {...}
                table.insert(Args, self)
                Tuple = {Object(unpack(Args))}
            else
                -- Right now, scope is not passed in to most seam objects
                Tuple = {Object(...)}
            end

            -- If nothing returns, don't bother running the rest of the code
            if #Tuple == 0 then
                return
            end

            -- But yeah, let's add created things to the trove
            for _, ThisValue in Tuple do
                self.Trove:Add(ThisValue)
            end

            -- Unpack the tuple and return it ALLLLLLLL
            return unpack(Tuple)
        end
    end

    function selfClass:InnerScope(NewScopedObjects : {[string] : any}?)
        -- Default to a blank table
        if NewScopedObjects == nil then
            NewScopedObjects = {}
        end

        -- Take the current scoped objects and copy them to the new table
        for Index, Object in self.ScopedObjects do
            NewScopedObjects[Index] = Object
        end

        local NewScope = Meta(NewScopedObjects) -- Make a new scope
        self.Trove:Add(NewScope) -- Add the new sub-scope to the parent trove

        return NewScope
    end

    function selfClass:AddObject(Object : any)
        self.Trove:Add(Object)
    end

    function selfClass:RemoveObject(Object : any)
        Object:Destroy()
        self.Trove[Object] = nil
    end

    function selfClass:Destroy()
        self.Trove:Destroy()
        self.Trove = nil
    end

    local ScopeInstance = setmetatable(selfClass, selfMeta)
    ScopeInstance.ScopedObjects = ScopedObjects
    ScopeInstance.Trove = Trove.new()

    return ScopeInstance
end

function Scope:__index(Key : string)
    if Key == "__SEAM_OBJECT" then
        return ClassSymbol
    elseif Key == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

return Meta :: ScopeConstructor


-- Author: iGottic

local Spring = {}

-- Constants
local EPSILON = 0.001

-- Imports
local Constructors = script.Parent.Parent.Parent
local Modules = Constructors.Parent.Modules
local GetValue = require(Constructors.Utilities.GetValue)
local StateManager = require(Modules.StateManager)
local ValuePacker = require(Modules.ValuePacker)
local Trove = require(Modules.Trove)
local Signal = require(Modules.Signal)
local Types = require(Modules.Types)
local IsValueChanged = require(Modules.IsValueChanged)
local Symbol = require(Modules.Symbol)
local UpdateSignals = require(Modules.UpdateSignals)

-- Variables
local ClassSymbol = Symbol.new("Spring")

-- Types Extended
export type SpringInstance<T> = {
    Velocity : T,
    Target : T
} & Types.BaseState<T>

export type SpringConstructor<T> = (Value : Types.BaseState<T> | any, Speed : Types.BaseState<any> | number, Dampening : Types.BaseState<any> | number) -> SpringInstance<T>

local function GetPositionAndVelocity(OldPosition : number, OldVelocity : number, Target : number, Dampening : number, Speed : number, TimeStarted : number) : (number, number)
    local TimePassed = os.clock() - TimeStarted
    local TimeValue = TimePassed * Speed
    local DampeningSquared = Dampening ^ 2
    local HighSpeedTime = nil
    local Cosine = nil
    local Sine = nil

    if DampeningSquared < 1 then
        -- Under dampened

        HighSpeedTime = (1 - DampeningSquared) ^ 0.5

        local EulersFastTime = math.exp(-Dampening * TimeValue) / HighSpeedTime
        Cosine = EulersFastTime * math.cos(HighSpeedTime * TimeValue)
        Sine = EulersFastTime * math.sin(HighSpeedTime * TimeValue)
    elseif DampeningSquared == 1 then
        -- Critically dampened

        HighSpeedTime = 1

        local EulersFastTime = math.exp(-Dampening * TimeValue)
        Cosine = EulersFastTime
        Sine = EulersFastTime * TimeValue
    else
        -- Over dampened

        HighSpeedTime = (DampeningSquared - 1) ^ 0.5
        Cosine = math.exp((-Dampening + HighSpeedTime) * TimeValue) / (2 * HighSpeedTime) + math.exp((-Dampening - HighSpeedTime) * TimeValue) / (2 * HighSpeedTime)
        Sine = math.exp((-Dampening + HighSpeedTime) * TimeValue) / (2 * HighSpeedTime) - math.exp((-Dampening - HighSpeedTime) * TimeValue) / (2 * HighSpeedTime)
    end

    local Value0 = {
        HighSpeedTime * Cosine + Dampening * Sine,
        1 - (HighSpeedTime * Cosine + Dampening * Sine),
        Sine / Speed,
    }

    local Value1 = {
        -Speed * Sine,
        Speed * Sine,
        HighSpeedTime * Cosine - Dampening * Sine,
    }

    local Position = Value0[1] * OldPosition + Value0[2] * Target + Value0[3] * OldVelocity
    local Velocity = Value1[1] * OldPosition + Value1[2] * Target + Value1[3] * OldVelocity

    return Position, Velocity
end

local function ConvertValueToUnpackedSprings(Value : any)
    local ValueType = typeof(Value)
    local UnpackedValue = ValuePacker.UnpackValue(Value, ValueType)

    for Index, Element in UnpackedValue do
        UnpackedValue[Index] = {
            StartingPosition = Element,
            Velocity = 0,
            StartingTime = os.clock(),
            Target = Element,
        }
    end

    return UnpackedValue
end

function Spring:__call(Value : Types.BaseState<any> | any, Speed : Types.BaseState<any> | number, Dampening : Types.BaseState<any> | number) : SpringInstance<any>
    local CurrentTarget = GetValue(Value)
    local ValueType = typeof(CurrentTarget)
    local UnpackedSprings = ConvertValueToUnpackedSprings(CurrentTarget)
    local TroveInstance = Trove.new()
    local AttachedSignal = Signal.new()
    local ChangedSignal = Signal.new()
    local CurrentValue = CurrentTarget
    local LastValue = CurrentTarget
    local InstanceSymbol = Symbol.new("Spring")

    TroveInstance:Add(UpdateSignals.OnFramePreUpdate:Connect(function()
        local PackedValues = {}

        for Index, Spring in UnpackedSprings do
            local Position, _ = GetPositionAndVelocity(Spring.StartingPosition, Spring.Velocity, Spring.Target, GetValue(Dampening), GetValue(Speed), Spring.StartingTime)

			if math.abs(Position) <= EPSILON then
				Position = 0
			elseif math.abs(Position - Spring.Target) <= EPSILON then
				Position = Spring.Target
			end

            PackedValues[Index] = Position
        end

        CurrentValue = ValuePacker.PackValue(PackedValues, ValueType)

        if IsValueChanged(LastValue, CurrentValue) then
            ChangedSignal:Fire("Value")
        end

        LastValue = CurrentValue
    end))

    local ActiveValue; ActiveValue = setmetatable({
        Destroy = function()
            UnpackedSprings = nil
            TroveInstance:Destroy()
            TroveInstance = nil
        end
    }, {
        __index = function(_, Index : string)
            if Index == "__SEAM_OBJECT" then
                return InstanceSymbol
            elseif Index == "Value" then
                return CurrentValue
            elseif Index == "Velocity" then
                local PackedValues = {}

                for ThisIndex, ThisSpring in UnpackedSprings do
                    local _, Velocity = GetPositionAndVelocity(ThisSpring.StartingPosition, ThisSpring.Velocity, ThisSpring.Target, GetValue(Dampening), GetValue(Speed), ThisSpring.StartingTime)

                    PackedValues[ThisIndex] = Velocity
                end

                return ValuePacker.PackValue(PackedValues, ValueType)
            elseif Index == "Changed" then
                return ChangedSignal
            elseif Index == "AttachedToInstance" then
                return AttachedSignal
            elseif Index == "Speed" then
                return Speed
            elseif Index == "Dampening" then
                return Dampening
            end

            return nil
        end,

        __newindex = function(_, Index : string, NewValue : any)
            if Index == "Target" then
                CurrentTarget = GetValue(NewValue)

                local UnpackedNewValue = ValuePacker.UnpackValue(CurrentTarget, ValueType)

                for ThisIndex, ThisSpring in UnpackedSprings do
                    local Position, Velocity = GetPositionAndVelocity(ThisSpring.StartingPosition, ThisSpring.Velocity, ThisSpring.Target, GetValue(Dampening), GetValue(Speed), ThisSpring.StartingTime)

                    ThisSpring.StartingPosition = Position
                    ThisSpring.Velocity = Velocity
                    ThisSpring.StartingTime = os.clock()
                    ThisSpring.Target = UnpackedNewValue[ThisIndex]
                end
            elseif Index == "Value" then
                local UnpackedNewValue = ValuePacker.UnpackValue(NewValue, ValueType)

                for ThisIndex, ThisSpring in UnpackedSprings do
                    local _, Velocity = GetPositionAndVelocity(ThisSpring.StartingPosition, ThisSpring.Velocity, ThisSpring.Target, GetValue(Dampening), GetValue(Speed), ThisSpring.StartingTime)

                    ThisSpring.StartingPosition = UnpackedNewValue[ThisIndex]
                    ThisSpring.Velocity = Velocity
                    ThisSpring.StartingTime = os.clock()
                end
            elseif Index == "Dampening" then
                Dampening = NewValue
            elseif Index == "Speed" then
                Speed = NewValue
            elseif Index == "Velocity" then
                local UnpackedNewValue = ValuePacker.UnpackValue(NewValue, ValueType)

                for ThisIndex, ThisSpring in UnpackedSprings do
                    local Position, _ = GetPositionAndVelocity(ThisSpring.StartingPosition, ThisSpring.Velocity, ThisSpring.Target, GetValue(Dampening), GetValue(Speed), ThisSpring.StartingTime)

                    ThisSpring.Velocity = UnpackedNewValue[ThisIndex]
                    ThisSpring.StartingPosition = Position
                    ThisSpring.StartingTime = os.clock()
                end
            end
        end,

        __call = function(This, Object, Index : string)
            AttachedSignal:Fire(Object)

            TroveInstance:Add(StateManager:AttachStateToObject(Object, {
                Value = function()
                    ChangedSignal:Fire("Value")
                    return This.Value
                end,

                PropertyName = Index
            }))

            return ActiveValue
        end
    })

    if typeof(Value) == "table" and Value.__SEAM_OBJECT then
        Value(ActiveValue, "Target")
    end

    return ActiveValue :: SpringInstance<any>
end

function Spring:__index(Index : string)
    if Index == "__SEAM_OBJECT" then
        return ClassSymbol
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, Spring)

return Meta :: SpringConstructor<any>


-- Author: iGottic

local Tween = {}

-- Constants
local EPSILON = 0.001

-- Services
local TweenService = game:GetService("TweenService")

-- Imports
local Constructors = script.Parent.Parent.Parent
local Modules = Constructors.Parent.Modules
local GetValue = require(Constructors.Utilities.GetValue)
local StateManager = require(Modules.StateManager)
local ValuePacker = require(Modules.ValuePacker)
local Trove = require(Modules.Trove)
local Signal = require(Modules.Signal)
local Types = require(Modules.Types)
local IsValueChanged = require(Modules.IsValueChanged)
local Symbol = require(Modules.Symbol)
local UpdateSignals = require(Modules.UpdateSignals)

-- Variables
local ClassSymbol = Symbol.new("Tween")

-- Types Extended
export type TweenInstance<T> = {
    Target : T
} & Types.BaseState<T>

export type TweenConstructor<T> = (Value : Types.BaseState<T> | any, TweenInformation : Types.BaseState<T> | TweenInfo) -> TweenInstance<T>

local function ConvertValueToUnpackedTweens(Value : any)
    local ValueType = typeof(Value)
    local UnpackedValue = ValuePacker.UnpackValue(Value, ValueType)

    for Index, Element in UnpackedValue do
        UnpackedValue[Index] = {Position0 = Element, Position1 = Element, Tick0 = os.clock()}
    end

    return UnpackedValue
end

function Tween:__call(Value : any, TweenInformation : Types.BaseState<any> | TweenInfo) : TweenInstance<any>
    local CurrentTarget = GetValue(Value)
    local ValueType = typeof(CurrentTarget)
    local UnpackedTweens = ConvertValueToUnpackedTweens(CurrentTarget)
    local TroveInstance = Trove.new()
    local AttachedSignal = Signal.new()
    local ChangedSignal = Signal.new()
    local CurrentValue = CurrentTarget
    local LastValue = CurrentTarget
    local InstanceSymbol = Symbol.new("Tween")

    TroveInstance:Add(UpdateSignals.OnFramePreUpdate:Connect(function()
        local PackedValues = {}
        local CurrentTweenInformation = GetValue(TweenInformation)

        for Index, ThisTween in UnpackedTweens do
            local Alpha = math.clamp((os.clock() - ThisTween.Tick0) / CurrentTweenInformation.Time, 0, 1)
            local UnitPosition = TweenService:GetValue(Alpha, CurrentTweenInformation.EasingStyle, CurrentTweenInformation.EasingDirection)
            local Position = ThisTween.Position0 + (ThisTween.Position1 - ThisTween.Position0) * UnitPosition

            if math.abs(Position) <= EPSILON then
				Position = 0
            elseif math.abs(Position - ThisTween.Position0) <= EPSILON then
				Position = ThisTween.Position0
			end

            PackedValues[Index] = Position
        end

        CurrentValue = ValuePacker.PackValue(PackedValues, ValueType)

        if IsValueChanged(LastValue, CurrentValue) then
            ChangedSignal:Fire("Value")
        end

        LastValue = CurrentValue
    end))

    local ActiveValue; ActiveValue = setmetatable({
        Destroy = function()
            UnpackedTweens = nil
            TroveInstance:Destroy()
            TroveInstance = nil
        end
    }, {
        __index = function(_, Index : string)
            if Index == "__SEAM_OBJECT" then
                return InstanceSymbol
            elseif Index == "Value" then
                return CurrentValue
            elseif Index == "Changed" then
                return ChangedSignal
            elseif Index == "AttachedToInstance" then
                return AttachedSignal
            elseif Index == "TweenInfo" then
                return TweenInformation
            end

            return nil
        end,

        __newindex = function(_, Index : string, NewValue : any)
            if Index == "Target" then
                CurrentTarget = GetValue(NewValue)

                local UnpackedTargetValue = ValuePacker.UnpackValue(CurrentTarget, ValueType)

                for ThisIndex, ThisTween in UnpackedTweens do
                    ThisTween.Position0 = ThisTween.Position1
                    ThisTween.Position1 = UnpackedTargetValue[ThisIndex]
                    ThisTween.Tick0 = os.clock()
                end
            elseif Index == "Value" then
                local UnpackedValue = ValuePacker.UnpackValue(NewValue, ValueType)

                for ThisIndex, ThisTween in UnpackedTweens do
                    ThisTween.Position0 = UnpackedValue[ThisIndex]
                    ThisTween.Position1 = UnpackedValue[ThisIndex]
                    ThisTween.Tick0 = os.clock()
                end
            elseif Index == "TweenInfo" then
                TweenInformation = NewValue
            end
        end,

        __call = function(This, Object, Index : string)
            AttachedSignal:Fire(Object)

            TroveInstance:Add(StateManager:AttachStateToObject(Object, {
                Value = function()
                    return This.Value
                end,

                PropertyName = Index
            }))

            return ActiveValue
        end
    })

    if typeof(Value) == "table" and Value.__SEAM_OBJECT then
        Value(ActiveValue, "Target")
    end

    return ActiveValue :: TweenInstance<any>
end

function Tween:__index(Index : string)
    if Index == "__SEAM_OBJECT" then
        return ClassSymbol
    elseif Index == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, Tween)

return Meta :: TweenConstructor<any>


-- Author: iGottic

local Computed = {}

-- Imports
local Modules = script.Parent.Parent.Parent.Modules
local StateManager = require(Modules.StateManager)
local Trove = require(Modules.Trove)
local Signal = require(Modules.Signal)
local Types = require(Modules.Types)
local Symbol = require(Modules.Symbol)
local Value = require(script.Parent.Value)
local GetValue = require(script.Parent.Parent.Utilities.GetValue)
local IsState = require(script.Parent.Parent.Utilities.IsState)

-- Variables
local ClassSymbol = Symbol.new("Computed")

-- Types Extended
export type ComputedInstance<T> = {} & Types.BaseState<T>
export type ComputedConstructor<T> = (Callback : ((Value : Value.ValueInstance<T>) -> any) -> any?) -> ComputedInstance<T>

function Computed:__call(Callback : ((Value : Value.ValueInstance<any>) -> any) -> any?)
    -- This shit has caused me so much pain
    local TroveInstance = Trove.new()
    local ChangedSignal = Signal.new()
    local UsedValues = {}
    local CurrentValue = nil
    local IsInitialized = false
    local AttachedSignal = Signal.new()
    local InstanceSymbol = Symbol.new("ComputedInstance")

    local function Use(ThisValue : Value.ValueInstance<any>)
        -- Is Use connecting to a state? If not, just get the value

        if ThisValue ~= nil and IsState(ThisValue) then
            if UsedValues[ThisValue] ~= nil then
                return GetValue(UsedValues[ThisValue])
            end

            UsedValues[ThisValue] = ThisValue

            TroveInstance:Add(ThisValue.Changed:Connect(function()
                CurrentValue = Callback(Use)
                ChangedSignal:Fire("Value", CurrentValue) -- When the state changes, fire the changed signal for computed
            end))
        end

        return GetValue(ThisValue)
    end

    local ActiveComputation; ActiveComputation = setmetatable({
        Destroy = function()
            TroveInstance:Destroy()
        end,
    }, {
        __call = function(_, Object : Instance, Index : string)
            AttachedSignal:Fire(Object)

            TroveInstance:Add(StateManager:AttachStateToObject(Object, {
                Value = function()
                    -- We don't want to re-calculate the computed when the states haven't changed,
                    -- so let's just force-calculate only when it's first checked

                    if not IsInitialized then
                        CurrentValue = Callback(Use)
                        IsInitialized = true
                    end

                    return CurrentValue
                end,

                PropertyName = Index
            }))

            return ActiveComputation
        end,

        __index = function(_, Index : string)
            if Index == "__SEAM_OBJECT" then
                return InstanceSymbol
            elseif Index == "Value" then
                -- Same as above, let's not re-calculate

                if not IsInitialized then
                    CurrentValue = Callback(Use)
                    IsInitialized = true
                end

                return CurrentValue
            elseif Index == "Changed" then
                -- We need to connect the Use() functions here to track changes

                if not IsInitialized then
                    CurrentValue = Callback(Use)
                    IsInitialized = true
                end

                return ChangedSignal
            elseif Index == "AttachedToInstance" then
                return AttachedSignal
            end

            return nil
        end
    })

    return ActiveComputation :: ComputedInstance<any>
end

function Computed:__index(Key : string)
    if Key == "__SEAM_INDEX" then
        return ClassSymbol
    elseif Key == "__SEAM_CAN_BE_SCOPED" then
        return true
    else
        return nil
    end
end

local Meta = setmetatable({}, Computed)

return Meta :: ComputedConstructor<any>