local GreenTea = require(script.Parent.GreenTea)
local isA = require(script.Parent.InstanceClasses)

local t = {}

type Typechecker = (...any) -> (boolean, any?)

type TypecheckerConstr<T... = ()> = (T...) -> Typechecker

local function asPlainFn(fn: (...any) -> ...any): Typechecker
	return function()
		return fn()
	end
end

local function asGreenTeaType(thing: any): any
	if GreenTea.isGtType(thing) then
		return thing
	elseif typeof(thing) == "function" then
		return GreenTea.custom(thing)
	elseif thing == nil then
		return GreenTea.none()
	else
		return GreenTea.typeof(thing)
	end
end

local implicitConstructors = {
	boolean = GreenTea.boolean :: Typechecker,
	buffer = (GreenTea.buffer :: any) :: Typechecker,
	callback = (GreenTea.anyfn :: any) :: Typechecker,
	["function"] = (GreenTea.anyfn :: any) :: Typechecker,
	none = (GreenTea.none :: any) :: Typechecker,
	["nil"] = (GreenTea.none :: any) :: Typechecker,
	string = asPlainFn(GreenTea.string),
	table = asPlainFn(GreenTea.anyTable),
	userdata = asPlainFn(GreenTea.userdata),
	vector = asPlainFn(GreenTea.vector),
	number = asPlainFn(GreenTea.number),
	thread = asPlainFn(GreenTea.coroutine),
	any = function()
		return GreenTea.any()
	end :: Typechecker,
	nan = function()
		return GreenTea.withCustom(GreenTea.number({ nan = true }), function(item)
			return item ~= item
		end) :: any
	end :: Typechecker,
	integer = function()
		return GreenTea.number({ integer = true }) :: any
	end :: Typechecker,
	numberPositive = function()
		return GreenTea.number({ range = "(0, inf]" }) :: any
	end :: Typechecker,
	numberNegative = function()
		return GreenTea.number({ range = "[-inf, 0)" }) :: any
	end :: Typechecker,
	Enum = (GreenTea.Enum :: any) :: Typechecker,
	EnumItem = (GreenTea.EnumItem :: any) :: Typechecker,
}

local function basicType(name: string): Typechecker
	return GreenTea.__newBasicType(name)
end

implicitConstructors.Axes = basicType("Axes")
implicitConstructors.BrickColor = basicType("BrickColor")
implicitConstructors.CatalogSearchParams = basicType("CatalogSearchParams")
implicitConstructors.CFrame = basicType("CFrame")
implicitConstructors.Color3 = basicType("Color3")
implicitConstructors.ColorSequence = basicType("ColorSequence")
implicitConstructors.ColorSequenceKeypoint = basicType("ColorSequenceKeypoint")
implicitConstructors.DateTime = basicType("DateTime")
implicitConstructors.DockWidgetPluginGuiInfo = basicType("DockWidgetPluginGuiInfo")
implicitConstructors.Enums = basicType("Enums")
implicitConstructors.Faces = basicType("Faces")
implicitConstructors.FloatCurveKey = basicType("FloatCurveKey")
implicitConstructors.Font = basicType("Font")
implicitConstructors.Instance = basicType("Instance")
implicitConstructors.NumberRange = basicType("NumberRange")
implicitConstructors.NumberSequence = basicType("NumberSequence")
implicitConstructors.NumberSequenceKeypoint = basicType("NumberSequenceKeypoint")
implicitConstructors.OverlapParams = basicType("OverlapParams")
implicitConstructors.PathWaypoint = basicType("PathWaypoint")
implicitConstructors.PhysicalProperties = basicType("PhysicalProperties")
implicitConstructors.Random = basicType("Random")
implicitConstructors.Ray = basicType("Ray")
implicitConstructors.RaycastParams = basicType("RaycastParams")
implicitConstructors.RaycastResult = basicType("RaycastResult")
implicitConstructors.RBXScriptConnection = basicType("RBXScriptConnection")
implicitConstructors.RBXScriptSignal = basicType("RBXScriptSignal")
implicitConstructors.Rect = basicType("Rect")
implicitConstructors.Region3 = basicType("Region3")
implicitConstructors.Region3int16 = basicType("Region3int16")
implicitConstructors.TweenInfo = basicType("TweenInfo")
implicitConstructors.UDim = basicType("UDim")
implicitConstructors.UDim2 = basicType("UDim2")
implicitConstructors.Vector2 = basicType("Vector2")
implicitConstructors.Vector2int16 = basicType("Vector2int16")
implicitConstructors.Vector3 = basicType("Vector3")
implicitConstructors.Vector3int16 = basicType("Vector3int16")

t.type = function(typename: string): any
	return GreenTea.isType(typename)
end :: TypecheckerConstr<string>

t.typeof = function(typename: string): any
	return GreenTea.isTypeof(typename)
end :: TypecheckerConstr<string>

t.literal = function(...: any): any
	local count = select("#", ...)
	if count == 0 then
		return GreenTea.any()
	elseif count == 1 then
		return GreenTea.literal(...)
	else
		local literals = {}
		for index = 1, count do
			table.insert(literals, GreenTea.literal(select(index, ...)))
		end

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

t.exactly = t.literal

t.keyOf = function(keyType: { [any]: any }): any
	local union = {}
	for key, _value in pairs(keyType) do
		table.insert(union, GreenTea.literal(key))
	end
	return GreenTea.union(table.unpack(union))
end :: TypecheckerConstr<{ [any]: any }>

t.valueOf = function(valueType: { [any]: any }): any
	local union = {}
	for _key, value in pairs(valueType) do
		table.insert(union, GreenTea.typeof(value))
	end
	return GreenTea.union(table.unpack(union))
end :: TypecheckerConstr<{ [any]: any }>

t.optional = function(thins: any): any
	return GreenTea.optional(asGreenTeaType(thins))
end :: TypecheckerConstr<any>

t.tuple = function(...: any): any
	local tupleArgs = table.pack(...)
	for index = 1, tupleArgs.n do
		tupleArgs[index] = asGreenTeaType(tupleArgs[index])
	end

	return GreenTea.tuple(table.unpack(tupleArgs))
end :: TypecheckerConstr<...any>

t.union = function(...: any)
	local unionArgs = table.pack(...)
	for index = 1, unionArgs.n do
		unionArgs[index] = asGreenTeaType(unionArgs[index])
	end
	return GreenTea.union(table.unpack(unionArgs))
end :: TypecheckerConstr<...any>

t.some = t.union

t.intersection = function(...: any): any
	local intersectionArgs = table.pack(...)
	for index = 1, intersectionArgs.n do
		intersectionArgs[index] = asGreenTeaType(intersectionArgs[index])
	end
	return GreenTea.intersection(table.unpack(intersectionArgs))
end :: TypecheckerConstr<...any>

t.every = t.intersection

t.keys = function(check: any): any
	return GreenTea.dictionary(asGreenTeaType(check), GreenTea.any())
end :: TypecheckerConstr<any>

t.values = function(check: any): any
	return GreenTea.dictionary(GreenTea.any(), asGreenTeaType(check))
end :: TypecheckerConstr<any>

t.map = function(key: any, value: any): any
	return GreenTea.dictionary(asGreenTeaType(key), asGreenTeaType(value))
end :: TypecheckerConstr<any, any>

t.set = function(key: any): any
	return GreenTea.dictionary(asGreenTeaType(key), GreenTea.literal(true))
end :: TypecheckerConstr<any>

t.numberMin = function(min: number): any
	return GreenTea.number({ range = `[{min}, inf]` })
end :: TypecheckerConstr<number>
t.numberMax = function(max: number): any
	return GreenTea.number({ range = `[-inf, {max}]` })
end :: TypecheckerConstr<number>
t.numberConstrained = function(min: number, max: number): any
	return GreenTea.number({ range = `[{min}, {max}]` })
end :: TypecheckerConstr<number, number>
t.numberMinExclusive = function(min: number): any
	return GreenTea.number({ range = `({min}, inf]` })
end :: TypecheckerConstr<number>
t.numberMaxExclusive = function(max: number): any
	return GreenTea.number({ range = `[-inf, {max})` })
end :: TypecheckerConstr<number>
t.numberConstrainedExclusive = function(min: number, max: number): any
	return GreenTea.number({ range = `({min}, {max})` })
end :: TypecheckerConstr<number, number>

t.match = function(pattern: string): any
	return GreenTea.string({ pattern = pattern })
end :: TypecheckerConstr<string>

t.array = function(typechecker): any
	return GreenTea.array(asGreenTeaType(typechecker))
end :: TypecheckerConstr<any>

t.strictArray = function(...: any): any
	local arrayTypes = { ... }
	arrayTypes[GreenTea.any()] = GreenTea.never()

	local count = select("#", ...)
	for index = 1, count do
		arrayTypes[index] = asGreenTeaType(arrayTypes[index])
	end

	return GreenTea.table(arrayTypes, { raw = true, count = { min = 0, max = count } })
end :: TypecheckerConstr<...any>

t.interface = function(interface: { [any]: any }): any
	local interfaceCleaned = {}
	for key, value in pairs(interface) do
		interfaceCleaned[key] = asGreenTeaType(value)
	end

	return GreenTea.table(interfaceCleaned, { raw = true })
end :: TypecheckerConstr<{ [any]: any }>

t.strictInterface = function(interface: { [string]: any }): any
	local interfaceCleaned = {}
	for key, value in pairs(interface) do
		interfaceCleaned[key] = asGreenTeaType(value)
	end

	interfaceCleaned[GreenTea.any()] = GreenTea.never()

	return GreenTea.table(interfaceCleaned, { raw = true })
end :: TypecheckerConstr<{ [string]: any }>

local function tChildren(children: { [string]: any })
	local childrenCleaned = {}
	for key, type in pairs(children) do
		assert(typeof(key) == "string", "children keys must be strings")
		childrenCleaned[key] = asGreenTeaType(type)
	end
	return function(thing): (boolean, string?)
		if not thing or typeof(thing) ~= "Instance" then
			return false, "expected an instance"
		end

		for key, typechecker in pairs(childrenCleaned) do
			if not thing:FindFirstChild(key) then
				return false, "missing child " .. key
			end

			local success, err = typechecker(thing:FindFirstChild(key))
			if not success then
				return false, err
			end
		end

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

			existing[child.Name] = true
		end

		return true, nil
	end
end

t.instanceOf = function(className: string, children: { [string]: any }?): any
	local childrenCheck = children and tChildren(children)
	return GreenTea.withCustom(isA(className), function(thing)
		if thing.ClassName ~= className then
			return false, "expected an instance of " .. className
		else
			if childrenCheck then
				return childrenCheck(thing)
			else
				return true
			end
		end
	end, "InstanceOf")
end :: TypecheckerConstr<string, { [string]: any }?>

t.instance = t.instanceOf

t.instanceIsA = function(className: string, children: { [string]: any }?): any
	if children then
		return GreenTea.withCustom(isA(className), tChildren(children), "Children")
	else
		return isA(className)
	end
end :: TypecheckerConstr<string, { [string]: any }?>

t.children = function(children: { [string]: any }): any
	return GreenTea.custom(tChildren(children), "Children")
end :: TypecheckerConstr<{ [string]: any }>

t.enum = function(enum: Enum): any
	return GreenTea.custom(function(thing)
		if typeof(thing) ~= "EnumItem" then
			return false, "expected an enum item"
		end
		if thing.EnumType ~= enum then
			return false, "expected an enum item of type " .. tostring(enum)
		end
		return true
	end, `{enum}`)
end :: TypecheckerConstr<Enum>

t.wrap = function<Args..., Returns...>(fn: (Args...) -> Returns..., argsCheck: any): (Args...) -> Returns...
	local argsChecker: GreenTea.Type = asGreenTeaType(argsCheck)
	return function(...: any)
		argsChecker:assert(...)

		return fn(...)
	end
end

t.strict = function<T>(typechecker: T): T
	local typechecker = asGreenTeaType(typechecker)
	return function(...)
		typechecker:assert(...)
	end :: any
end

type exportedTypes = typeof(t) & typeof(implicitConstructors)

local exportedRuntime = setmetatable(t :: any, {
	__index = function(_self, key)
		if implicitConstructors[key] then
			return implicitConstructors[key]()
		else
			return nil
		end
	end,
})

return exportedRuntime :: exportedTypes


--!strict
--!optimize 2

local Jecs = require("../Jecs")
local Types = require("../Types")

local RawInst = require("./Small/RawInst")
local Constants = require("../Constants")
local stickToRightEdge = require("./Small/StickToRightEdge")
local RulerPool = require("./RulerPool")

local rawnewindex = RawInst.rawnewindex
local rawindex = RawInst.rawindex

local profilebegin, profileend = debug.profilebegin, debug.profileend

type Manager = Types.Manager
type World = Types.World
type ExplorerEntity = Types.ExplorerEntity
type ExplorerFrame = Types.ExplorerFrame

local lastScrollTime = 0

local heightCache = {}
local heightCacheVersion = 0
local lastHeightCacheVersion = -1
-- Track which entities need height recalculation
local dirtyHeights = {}
local lastInfo: Types.VisibleViewportInfo?

local function calculateDeepHeightOptimized(
	world: World,
	expanded: Jecs.Id,
	queryExpanded: Jecs.Id,
	childrenMap: {[ExplorerEntity]: {ExplorerEntity}},
	childAmountCache: {[ExplorerEntity]: number},
	isExplorerEntityInvisible: (ExplorerEntity) -> boolean,
	isExplorerEntityDeleted: (ExplorerEntity) -> boolean,
	entity: ExplorerEntity
): number
	if childAmountCache[entity] == 0 then return 1 end
	local isExpanded = world:has(entity, expanded) or world:has(entity, queryExpanded)
	if not isExpanded then
		return 1
	end
	if not dirtyHeights[entity] and heightCache[entity] then
		return heightCache[entity]
	end
	local children = childrenMap[entity]
	if not children or #children == 0 then
		heightCache[entity] = 1
		dirtyHeights[entity] = nil
		return 1
	end
	local totalHeight = 1
	for _, child in children do
		if not (isExplorerEntityInvisible(child) or isExplorerEntityDeleted(child)) then
			local childIsExpanded = world:has(child, expanded) or world:has(child, queryExpanded)
			if childIsExpanded then
				totalHeight += calculateDeepHeightOptimized(world, expanded, queryExpanded, childrenMap, childAmountCache, isExplorerEntityInvisible, isExplorerEntityDeleted, child)
			else
				totalHeight += 1
			end
		end
	end

	heightCache[entity] = totalHeight
	dirtyHeights[entity] = nil
	return totalHeight
end

local VirtualScroller = {IsScrolling = false, IsVirtualScrollerUpdating = false} :: Types.VirtualScroller

function VirtualScroller.clearHeightCache()
	table.clear(heightCache)
	table.clear(dirtyHeights)
	heightCacheVersion += 1
end

function VirtualScroller.invalidateEntityHeightCache(Manager, entity)
	heightCache[entity] = nil
	dirtyHeights[entity] = true
	local parent = Manager.parentOf(entity)
	while parent do
		heightCache[parent] = nil
		dirtyHeights[parent] = true
		parent = Manager.parentOf(parent)
	end

	heightCacheVersion += 1
end

function VirtualScroller.getVisibleViewportInfo(Manager, top, isScroller)
	profilebegin("Jexplorer::getVisibleViewportInfo")
	local height: number = Manager.getSetting("FrameHeight")
	local indentWidth: number = Manager.getSetting("IndentWidth")
	local frameBuffer = Manager.getSetting("VirtualScrollFrameBuffer")
	local viewportHeight = Manager.InstTreeAbsoluteSizeY

	local viewportBuffer = height * frameBuffer
	local viewportTop = top - viewportBuffer
	local viewportBottom = top + viewportHeight + viewportBuffer

	local flattenedEntities, entityToIndex, indexToIndentOffset =
		Manager.HierarchyState.getFlattenedVisibleEntities(Manager, {}, isScroller)
	local flatCount = #flattenedEntities

	local startIndex = math.max(1, math.floor(viewportTop / height) + 1)
	local endIndex = math.min(flatCount, math.ceil(viewportBottom / height) + 1)

	local visible = {}
	local visibleN = 1

	local expandedHeightIndex = {}

	local isEntityExpanded = Manager.isExplorerEntityExpanded
	local isEntityInvisible = Manager.isExplorerEntityInvisible
	local isEntityDeleted = Manager.isExplorerEntityDeleted
	local components = Manager.Components
	local world = Manager.WidgetState.World
	local HierarchyState = Manager.HierarchyState
	local stateCache = HierarchyState.StateCache

	local expandedParents = {}
	local expandedParentsSet = {}
	local numExpandedParents = 0

	profilebegin("visible check")
	local visibleSet = {}
	local entityToVisibleIndex = {}
	for index = startIndex, endIndex do
		local entity = flattenedEntities[index]
		local absoluteYOffset = height * (index - 1)

		local parent = Manager.parentOf(entity)
		if parent and not expandedParentsSet[parent] and isEntityExpanded(parent) then
			numExpandedParents += 1
			expandedParents[numExpandedParents] = parent
			expandedParentsSet[parent] = true
		end

		visibleSet[entity] = true
		visible[visibleN] = {
			entity = entity,
			yoffset = absoluteYOffset,
			indentoffset = indexToIndentOffset[index] * indentWidth,
		}
		entityToVisibleIndex[entity] = visibleN
		visibleN += 1
	end
	profileend()

	profilebegin("expanded check")
	for _, entity in expandedParents do
		local parent = Manager.parentOf(entity)
		if parent and not expandedParentsSet[parent] and isEntityExpanded(parent) then
			numExpandedParents += 1
			expandedParents[numExpandedParents] = parent

			expandedParentsSet[parent] = true
		end
	end

	local stickyStackDepth = 0
	local visibleExpandedAncestors
	if numExpandedParents > 0 then
		if numExpandedParents >= 2 then
			table.sort(expandedParents, function(a, b)
				return (entityToIndex[a] or 0) < (entityToIndex[b] or 0)
			end)
		end
		visibleExpandedAncestors = {}

		for _, entity in expandedParents do
			local index = entityToIndex[entity]
			if not index then continue end

			local absoluteYOffset = height * (index - 1)

			local expandedHeight = calculateDeepHeightOptimized(
				world, components.Expanded, components.QueryExpanded,
				HierarchyState.ChildrenMap, Manager.ChildAmountCache,
				isEntityInvisible, isEntityDeleted, entity
			)
			expandedHeightIndex[entity] = expandedHeight

			local expandedBottomOffset = absoluteYOffset + (height * expandedHeight)

			local childrenAreVisible =
				expandedBottomOffset > top                    -- real, no buffer
				and absoluteYOffset < (top + viewportHeight)  -- real, no buffer

			if not childrenAreVisible then continue end

			local indentOffset = indexToIndentOffset[index] * indentWidth
			local stickyStackBottom = stickyStackDepth  * height  -- bottom of all currently pinned frames

			local isScrolledPast = absoluteYOffset <= (top + stickyStackBottom)

			local stickyY: number? = nil
			local rulerHeight: number

			if isScrolledPast then
				local nextStickyBottom = (stickyStackDepth + 1) * height
				local tailTop = expandedBottomOffset - top - height

				if nextStickyBottom > tailTop then
					rulerHeight = (expandedHeight - 1) * height
					stickyY = nil
				else
					local _stickyY = stickyStackDepth * height
					stickyStackDepth += 1
					rulerHeight = (expandedBottomOffset - top - _stickyY) - height
					stickyY = _stickyY
				end
			else
				rulerHeight = (expandedHeight - 1) * height
			end

			if not visibleSet[entity] or isScrolledPast then
				visibleSet[entity] = true
				visible[visibleN] = {
					entity = entity,
					yoffset = absoluteYOffset,
					indentoffset = indentOffset,
				}
				visibleExpandedAncestors[visibleN] = {
					stickyY = stickyY,
					rulerHeight = rulerHeight,
				}
				entityToVisibleIndex[entity] = visibleN
				visibleN += 1
			end
		end
	end

	profileend()

	profileend()
	return {
		visibleArray = visible,
		entityToIndex = entityToIndex,
		explorerFrameHeight = height,
		expandedHeightIndex = expandedHeightIndex,
		flatCount = flatCount,
		visibleExpandedAncestors = visibleExpandedAncestors or {},
		stickyStackDepth = stickyStackDepth,
		viewportTop = viewportTop,
		entityToVisibleIndex = entityToVisibleIndex,
		visibleSet = visibleSet
	}
end

function VirtualScroller.getLastVisibleViewportInfo(Manager)
	return lastInfo
end

local isModifyingInstances = false
local lastCanvasPos = Vector2.zero
function VirtualScroller.update(Manager, isScroller: boolean, forceUpdateIcons: boolean?)
	local state = Manager.WidgetState
	local components = Manager.Components

	local jexplorerUi = state.JExplorerUI
	local frameWorld = state.FrameWorld
	local world = state.World

	local ExplorerFrame = Manager.ExplorerFrame
	local HierarchyState = Manager.HierarchyState

	if isScroller and VirtualScroller.IsVirtualScrollerUpdating then
		return
	end

	profilebegin("Jexplorer::updateVirtualScroller")

	VirtualScroller.IsVirtualScrollerUpdating = true

	local indentWidth: number = Manager.getSetting("IndentWidth")
	local scrollStep = Manager.getSetting("ScrollStep")
	local rawPos = state.TreeScroller.CanvasPosition
	local top = math.floor(rawPos.Y / scrollStep) * scrollStep

	local viewportInfo = VirtualScroller.getVisibleViewportInfo(Manager, top, isScroller)

	local currentTime = os.clock()
	local any = false
	local invisible = {}

	profilebegin("invisible check")
	if lastInfo then
		for _, info in lastInfo.visibleArray do
			local entity = info.entity
			if not viewportInfo.visibleSet[entity] then
				table.insert(invisible, entity)
			end
		end
	end
	lastInfo = viewportInfo
	profileend()

	for _, entity in invisible do
		local frame = world:get(entity, components.Frame)
		if frame then
			local frameState = frameWorld:get(frame, components.FrameState)
			if frameState then
				frameState.isSticky = false
			end
			RulerPool.releaseRuler(Manager, frame)  -- release before reuse
			ExplorerFrame.queueForExplorerFrameReuse(Manager, frame, entity)
			any = true
		end
	end
	if any then
		ExplorerFrame.flushFrameReuseQueue(Manager)
	end

	local stickyStackHeight = viewportInfo.stickyStackDepth * viewportInfo.explorerFrameHeight

	local parentOf = Manager.parentOf
	local frameOf = Manager.frameOf
	local assign = ExplorerFrame.assignExplorerFrame
	local reuse = ExplorerFrame.queueForExplorerFrameReuse
	local allocateFrame = ExplorerFrame.getExplorerFrame

	local isEntityInvisible = Manager.isExplorerEntityInvisible
	local childAmountCache = Manager.ChildAmountCache
	local stateCache = HierarchyState.StateCache
	local scrollContainer = state.InstanceTree
	local childrenMap = HierarchyState.ChildrenMap

	local scrollPos = Vector2.new(rawPos.X, top)
	local smoothed = lastCanvasPos:Lerp(scrollPos, 0.8)
	local xMoved = math.abs(lastCanvasPos.X - scrollPos.X) > 0

	lastCanvasPos = scrollPos

	local lockedInstanceModification = not isModifyingInstances
	isModifyingInstances = true

	local classNameCache = Manager.ClassNameCache
	local nameCache = Manager.NameCache

	local scrollingFrame = state.TreeScroller
	local containerWidth = scrollingFrame.AbsoluteSize.X
	local maxNameBoundsX = 0

	local stickyStacksEnabled = Manager.getSetting("StickyStacksEnabled")

	local updateRightEdgeIcons = {}

	for i, info in viewportInfo.visibleArray do
		local entity, absoluteYOffset = info.entity, info.yoffset
		local instance = world:get(entity, components.Instance) :: Instance

		local frame = world:get(entity, components.Frame)

		if not frame then
			local f = allocateFrame(Manager)
			world:set(entity, components.Frame, f)
			frame = f
		end

		if not frame then continue end

		local frameInst = frameOf(frame)
		if not frameInst then continue end

		local frameState = state.FrameWorld:get(frame, components.FrameState)
		if not frameState then continue end

		if not isScroller then
			local children = childrenMap[entity]
			if children then
				childAmountCache[entity] = #children
			else
				childAmountCache[entity] = 0
			end
		end
		if not lockedInstanceModification then continue end

		local frameX = -smoothed.X + info.indentoffset
		local frameY
		local ancestorInfo = viewportInfo.visibleExpandedAncestors[i]

		local isSticky
		if ancestorInfo and ancestorInfo.stickyY then
			if stickyStacksEnabled then
				isSticky = true
				frameWorld:add(frame, components.FRAMEWORLD_IsSticky)
				frameY = ancestorInfo.stickyY
				RulerPool.assignRuler(Manager, frame, frameX, ancestorInfo.stickyY, ancestorInfo.rulerHeight, indentWidth)
			else
				frameY = -smoothed.Y + absoluteYOffset
				local expandedHeight = viewportInfo.expandedHeightIndex[entity]
				RulerPool.assignRuler(Manager, frame, frameX, frameY, (expandedHeight - 1) * viewportInfo.explorerFrameHeight, indentWidth)
			end
		else
			isSticky = false
			frameWorld:remove(frame, components.FRAMEWORLD_IsSticky)
			frameY = -smoothed.Y + absoluteYOffset
			local expandedHeight = viewportInfo.expandedHeightIndex[entity]
			if expandedHeight then
				if stickyStacksEnabled then
					if frameY > stickyStackHeight then
						RulerPool.assignRuler(Manager, frame, frameX, frameY, (expandedHeight - 1) * viewportInfo.explorerFrameHeight, indentWidth)
					elseif frameWorld:has(frame, components.FRAMEWORLD_Ruler) then
						RulerPool.releaseRuler(Manager, frame)
					end
				else
					RulerPool.assignRuler(Manager, frame, frameX, frameY, (expandedHeight - 1) * viewportInfo.explorerFrameHeight, indentWidth)
				end
			else
				RulerPool.releaseRuler(Manager, frame)
			end
		end

		do
			local nameBoundsX = info.indentoffset + frameState.currentNameBoundX
			if nameBoundsX > maxNameBoundsX then
				maxNameBoundsX = nameBoundsX
			end
		end

		frameState.lastFrameX = frameState.currentFrameX
		frameState.currentFrameX = frameX

		local className = classNameCache[entity]

		frameInst.Position = UDim2.fromOffset(frameX, frameY)

		-- arrow
		do
			local arrow = frameState.arrow
			if world:has(entity, components.ArrowInvisible) then
				arrow.Visible = false
			else
				if className == "DataModel" or childAmountCache[entity] == 0 then
					arrow.Visible = false
				else
					arrow.Visible = true
				end
				if Manager.isExplorerEntityExpanded(entity) then
					arrow.Rotation = 0
				else
					arrow.Rotation = -90
				end
			end
		end

		-- special name states
		do
			local newName

			if className == "Script" then
				newName = Constants.ExplorerFrameScriptClassNameFormat:format(
					rawindex(instance, "RunContext").Name
				)
			else
				newName = Constants.ExplorerFrameNameFormat:format(className)
			end

			if newName ~= frameState.currentFrameName then
				rawnewindex(frameInst, "Name", newName)
				frameState.currentFrameName = newName
			end
		end

		-- text
		rawnewindex(frameState.instanceName, "Text", nameCache[entity])

		-- assign frame
		local entityState = stateCache[entity]
		local isTopLevel = if entityState ~= nil then entityState.isTopLevel else world:has(entity, components.DisplayTopLevel)

		local isnewlyassigned = assign(Manager, frame, entity, parentOf(entity), instance.Parent == game or isTopLevel, true)
		if (not isnewlyassigned) and (forceUpdateIcons or world:has(entity, components.HasSideIcons)) then
			table.insert(updateRightEdgeIcons, {frame = frame, frameX = if isScroller then frameX else nil, entity = entity, frameState = frameState})
		end
		if stickyStacksEnabled then
			if isSticky then
				frameState.overlayFrame.BackgroundTransparency = 0.01
				frameInst.ZIndex = 10
			else
				frameState.overlayFrame.BackgroundTransparency = 0
				frameInst.ZIndex = 0
			end
		end
	end
	for _, args in updateRightEdgeIcons do
		ExplorerFrame.updateRightEdgeIcons(Manager,
			args.frame, containerWidth, args.frameX, args.entity, args.frameState
		)
	end

	if lockedInstanceModification then
		isModifyingInstances = false
	end

	for frame in state.FrameWorld:query(components.Instance):without(components.Entity) do
		ExplorerFrame.queueForExplorerFrameReuse(Manager, frame)
	end

	local size = UDim2.new(
		0, maxNameBoundsX + 80, -- Enough padding for 3 right edge icons
		0, (viewportInfo.flatCount + 3) * viewportInfo.explorerFrameHeight
	)

	Manager.DeferredTaskScheduler.FrameTagAssignQueuePending = true
	state.TreeScroller.CanvasSize = size

	VirtualScroller.IsVirtualScrollerUpdating = false
	profileend()
	return viewportInfo
end

function VirtualScroller.rebuildAndUpdate(Manager, isScroller)
	Manager.HierarchyState.rebuildHierarchyCache(Manager)
	VirtualScroller.update(Manager, isScroller, true)
end

function VirtualScroller.trackScrolling(Manager)
	local state = Manager.WidgetState
	local lastScrollTime = -math.huge -- never triggered yet
	local settleTime = Manager.getSetting("VirtualScrollSettleTime")

	local c = state.TreeScroller:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
		-- Any signal fire = scrolling. Let the settle timer decide when it stops.
		lastScrollTime = os.clock()

		if not VirtualScroller.IsScrolling then
			VirtualScroller.IsScrolling = true
		end
	end)

	local t = task.spawn(function()
		while true do
			task.wait(1/30)
			-- Only flip to false if we were scrolling AND enough time has passed
			-- without a new scroll event
			if VirtualScroller.IsScrolling and (os.clock() - lastScrollTime) > settleTime then
				VirtualScroller.IsScrolling = false
			end
		end
	end)

	return c, t
end

return VirtualScroller


type Source<T> = (() -> (T)) & ((newValue: T) -> ())
type V<T> = T?--T | Source<T>

-- Base Instance type
type Instance = {
	Name: V<string>,
	ClassName: V<string>,
	Archivable: V<boolean>,
	Parent: V<(Instance | any)?>,
	[number]: any | string,
}

-- GuiBase2d - Base for all 2D UI elements
type GuiBase2d = Instance & {
	AbsolutePosition: V<Vector2>,
	AbsoluteRotation: V<number>,
	AbsoluteSize: V<Vector2>,
	AutoLocalize: V<boolean>,
	Clip: V<boolean>,
	Localize: V<boolean>,
	RootLocalizationTable: V<LocalizationTable?>,
	Selectable: V<boolean>,
	SelectionBehaviorDown: V<Enum.SelectionBehavior>,
	SelectionBehaviorLeft: V<Enum.SelectionBehavior>,
	SelectionBehaviorRight: V<Enum.SelectionBehavior>,
	SelectionBehaviorUp: V<Enum.SelectionBehavior>,
	SelectionGroup: V<boolean>,
	SelectionImageObject: V<GuiObject?>,
	SelectionOrder: V<number>,
}

-- GuiObject - Base for all interactive UI elements
type GuiObject = GuiBase2d & {
	Active: V<boolean>,
	AnchorPoint: V<Vector2>,
	AutomaticSize: V<Enum.AutomaticSize>,
	BackgroundColor3: V<Color3>,
	BackgroundTransparency: V<number>,
	BorderColor3: V<Color3>,
	BorderMode: V<Enum.BorderMode>,
	BorderSizePixel: V<number>,
	LayoutOrder: V<number>,
	NextSelectionDown: V<GuiObject?>,
	NextSelectionLeft: V<GuiObject?>,
	NextSelectionRight: V<GuiObject?>,
	NextSelectionUp: V<GuiObject?>,
	Position: V<UDim2>,
	Rotation: V<number>,
	Size: V<UDim2>,
	SizeConstraint: V<Enum.SizeConstraint>,
	Visible: V<boolean>,
	ZIndex: V<number>,
}

-- GuiButton - Base for clickable UI elements
type GuiButton = GuiObject & {
	AutoButtonColor: V<boolean>,
	Modal: V<boolean>,
	Selected: V<boolean>,
	Style: V<Enum.ButtonStyle>,
}

-- Frame
export type Frame = GuiObject & {
	Style: V<Enum.FrameStyle>,
}

-- ScrollingFrame
export type ScrollingFrame = GuiObject & {
	AutomaticCanvasSize: V<Enum.AutomaticSize>,
	BottomImage: V<string>,
	CanvasPosition: V<Vector2>,
	CanvasSize: V<UDim2>,
	ElasticBehavior: V<Enum.ElasticBehavior>,
	HorizontalScrollBarInset: V<Enum.ScrollBarInset>,
	MidImage: V<string>,
	ScrollBarImageColor3: V<Color3>,
	ScrollBarImageTransparency: V<number>,
	ScrollBarThickness: V<number>,
	ScrollingDirection: V<Enum.ScrollingDirection>,
	ScrollingEnabled: V<boolean>,
	TopImage: V<string>,
	VerticalScrollBarInset: V<Enum.ScrollBarInset>,
	VerticalScrollBarPosition: V<Enum.VerticalScrollBarPosition>,
}

-- TextLabel
export type TextLabel = GuiObject & {
	ContentText: V<string>,
	Font: V<Enum.Font>,
	FontFace: V<Font>,
	FontSize: V<Enum.FontSize>,
	LineHeight: V<number>,
	LocalizationMatchIdentifier: V<string>,
	LocalizationMatchedSourceText: V<string>,
	MaxVisibleGraphemes: V<number>,
	RichText: V<boolean>,
	Text: V<string>,
	TextBounds: V<Vector2>,
	TextColor3: V<Color3>,
	TextDirection: V<Enum.TextDirection>,
	TextFits: V<boolean>,
	TextScaled: V<boolean>,
	TextSize: V<number>,
	TextStrokeColor3: V<Color3>,
	TextStrokeTransparency: V<number>,
	TextTransparency: V<number>,
	TextTruncate: V<Enum.TextTruncate>,
	TextWrapped: V<boolean>,
	TextXAlignment: V<Enum.TextXAlignment>,
	TextYAlignment: V<Enum.TextYAlignment>,
}

-- TextButton
export type TextButton = GuiButton & {
	ContentText: V<string>,
	Font: V<Enum.Font>,
	FontFace: V<Font>,
	FontSize: V<Enum.FontSize>,
	LineHeight: V<number>,
	LocalizationMatchIdentifier: V<string>,
	LocalizationMatchedSourceText: V<string>,
	MaxVisibleGraphemes: V<number>,
	RichText: V<boolean>,
	Text: V<string>,
	TextBounds: V<Vector2>,
	TextColor3: V<Color3>,
	TextDirection: V<Enum.TextDirection>,
	TextFits: V<boolean>,
	TextScaled: V<boolean>,
	TextSize: V<number>,
	TextStrokeColor3: V<Color3>,
	TextStrokeTransparency: V<number>,
	TextTransparency: V<number>,
	TextTruncate: V<Enum.TextTruncate>,
	TextWrapped: V<boolean>,
	TextXAlignment: V<Enum.TextXAlignment>,
	TextYAlignment: V<Enum.TextYAlignment>,
}

-- TextBox
export type TextBox = GuiObject & {
	ClearTextOnFocus: V<boolean>,
	ContentText: V<string>,
	CursorPosition: V<number>,
	Font: V<Enum.Font>,
	FontFace: V<Font>,
	FontSize: V<Enum.FontSize>,
	LineHeight: V<number>,
	LocalizationMatchIdentifier: V<string>,
	LocalizationMatchedSourceText: V<string>,
	MaxVisibleGraphemes: V<number>,
	MultiLine: V<boolean>,
	PlaceholderColor3: V<Color3>,
	PlaceholderText: V<string>,
	RichText: V<boolean>,
	SelectionStart: V<number>,
	ShowNativeInput: V<boolean>,
	Text: V<string>,
	TextBounds: V<Vector2>,
	TextColor3: V<Color3>,
	TextDirection: V<Enum.TextDirection>,
	TextEditable: V<boolean>,
	TextFits: V<boolean>,
	TextScaled: V<boolean>,
	TextSize: V<number>,
	TextStrokeColor3: V<Color3>,
	TextStrokeTransparency: V<number>,
	TextTransparency: V<number>,
	TextTruncate: V<Enum.TextTruncate>,
	TextWrapped: V<boolean>,
	TextXAlignment: V<Enum.TextXAlignment>,
	TextYAlignment: V<Enum.TextYAlignment>,
}

-- ImageLabel
export type ImageLabel = GuiObject & {
	Image: V<string>,
	ImageColor3: V<Color3>,
	ImageRectOffset: V<Vector2>,
	ImageRectSize: V<Vector2>,
	ImageTransparency: V<number>,
	ResampleMode: V<Enum.ResamplerMode>,
	ScaleType: V<Enum.ScaleType>,
	SliceCenter: V<Rect>,
	SliceScale: V<number>,
	TileSize: V<UDim2>,
}

-- ImageButton
export type ImageButton = GuiButton & {
	HoverImage: V<string>,
	Image: V<string>,
	ImageColor3: V<Color3>,
	ImageRectOffset: V<Vector2>,
	ImageRectSize: V<Vector2>,
	ImageTransparency: V<number>,
	PressedImage: V<string>,
	ResampleMode: V<Enum.ResamplerMode>,
	ScaleType: V<Enum.ScaleType>,
	SliceCenter: V<Rect>,
	SliceScale: V<number>,
	TileSize: V<UDim2>,
}

-- VideoFrame
export type VideoFrame = GuiObject & {
	IsLoaded: V<boolean>,
	Resolution: V<Vector2>,
	TimeLength: V<number>,
	TimePosition: V<number>,
	Video: V<string>,
	Volume: V<number>,
}

-- ViewportFrame
export type ViewportFrame = GuiObject & {
	Ambient: V<Color3>,
	CurrentCamera: V<Camera?>,
	ImageColor3: V<Color3>,
	ImageTransparency: V<number>,
	LightColor: V<Color3>,
	LightDirection: V<Vector3>,
}

-- CanvasGroup
export type CanvasGroup = GuiObject & {
	GroupColor3: V<Color3>,
	GroupTransparency: V<number>,
}

-- UI Layout Objects
export type UILayout = Instance & {

}

export type UIListLayout = UILayout & {
	FillDirection: V<Enum.FillDirection>,
	HorizontalAlignment: V<Enum.HorizontalAlignment>,
	Padding: V<UDim>,
	SortOrder: V<Enum.SortOrder>,
	VerticalAlignment: V<Enum.VerticalAlignment>,
	Wraps: V<boolean>,
}

export type UIGridLayout = UILayout & {
	CellPadding: V<UDim2>,
	CellSize: V<UDim2>,
	FillDirection: V<Enum.FillDirection>,
	FillDirectionMaxCells: V<number>,
	HorizontalAlignment: V<Enum.HorizontalAlignment>,
	SortOrder: V<Enum.SortOrder>,
	StartCorner: V<Enum.StartCorner>,
	VerticalAlignment: V<Enum.VerticalAlignment>,
}

export type UITableLayout = UILayout & {
	FillEmptySpaceColumns: V<boolean>,
	FillEmptySpaceRows: V<boolean>,
	MajorAxis: V<Enum.TableMajorAxis>,
	Padding: V<UDim2>,
}

export type UIPageLayout = UILayout & {
	Animated: V<boolean>,
	Circular: V<boolean>,
	CurrentPage: V<GuiObject?>,
	EasingDirection: V<Enum.EasingDirection>,
	EasingStyle: V<Enum.EasingStyle>,
	FillDirection: V<Enum.FillDirection>,
	GamepadInputEnabled: V<boolean>,
	HorizontalAlignment: V<Enum.HorizontalAlignment>,
	Padding: V<UDim>,
	ScrollWheelInputEnabled: V<boolean>,
	SortOrder: V<Enum.SortOrder>,
	TouchInputEnabled: V<boolean>,
	TweenTime: V<number>,
	VerticalAlignment: V<Enum.VerticalAlignment>,
}

-- UI Constraint Objects
export type UIConstraint = Instance & {

}

export type UIAspectRatioConstraint = UIConstraint & {
	AspectRatio: V<number>,
	AspectType: V<Enum.AspectType>,
	DominantAxis: V<Enum.DominantAxis>,
}

export type UISizeConstraint = UIConstraint & {
	MaxSize: V<Vector2>,
	MinSize: V<Vector2>,
}

export type UITextSizeConstraint = UIConstraint & {
	MaxTextSize: V<number>,
	MinTextSize: V<number>,
}

-- UI Effect Objects
export type UICorner = Instance & {
	CornerRadius: V<UDim>,
}

export type UIStroke = Instance & {
	ApplyStrokeMode: V<Enum.ApplyStrokeMode>,
	Color: V<Color3>,
	LineJoinMode: V<Enum.LineJoinMode>,
	Thickness: V<number>,
	Transparency: V<number>,
}

export type UIGradient = Instance & {
	Color: V<ColorSequence>,
	Offset: V<Vector2>,
	Rotation: V<number>,
	Transparency: V<NumberSequence>,
}

export type UIPadding = Instance & {
	PaddingBottom: V<UDim>,
	PaddingLeft: V<UDim>,
	PaddingRight: V<UDim>,
	PaddingTop: V<UDim>,
}

export type UIScale = Instance & {
	Scale: V<number>,
}

-- Screen and Layer GUIs
export type LayerCollector = GuiBase2d & {
	Enabled: V<boolean>,
	ResetOnSpawn: V<boolean>,
	ZIndexBehavior: V<Enum.ZIndexBehavior>,
}

export type ScreenGui = LayerCollector & {
	ClipToDeviceSafeArea: V<boolean>,
	DisplayOrder: V<number>,
	IgnoreGuiInset: V<boolean>,
	SafeAreaCompatibility: V<Enum.SafeAreaCompatibility>,
	ScreenInsets: V<Enum.ScreenInsets>,
}

export type BillboardGui = LayerCollector & {
	Active: V<boolean>,
	Adornee: V<Instance?>,
	AlwaysOnTop: V<boolean>,
	Brightness: V<number>,
	ClipsDescendants: V<boolean>,
	DistanceStep: V<number>,
	DistanceLowerLimit: V<number>,
	DistanceUpperLimit: V<number>,
	ExtentsOffset: V<Vector3>,
	ExtentsOffsetWorldSpace: V<Vector3>,
	Face: V<Enum.NormalId>,
	LightInfluence: V<number>,
	MaxDistance: V<number>,
	PlayerToHideFrom: V<Player?>,
	Size: V<UDim2>,
	SizeOffset: V<Vector2>,
	StudsOffset: V<Vector3>,
	StudsOffsetWorldSpace: V<Vector3>,
}

export type SurfaceGui = LayerCollector & {
	Active: V<boolean>,
	Adornee: V<Instance?>,
	AlwaysOnTop: V<boolean>,
	Brightness: V<number>,
	CanvasSize: V<Vector2>,
	ClipsDescendants: V<boolean>,
	Face: V<Enum.NormalId>,
	LightInfluence: V<number>,
	MaxDistance: V<number>,
	PixelsPerStud: V<number>,
	SizingMode: V<Enum.SurfaceGuiSizingMode>,
	ToolPunchThroughDistance: V<number>,
	ZOffset: V<number>,
}

return nil


local Props = require(script.ClassProps)

type Create<Name, PropsType, Type> = (Name) -> (PropsType) -> (Type)
type Create_Overloaded =
	& Create<"Frame", Props.Frame, Frame>
	& Create<"ScrollingFrame", Props.ScrollingFrame, ScrollingFrame>
	& Create<"TextLabel", Props.TextLabel, TextLabel>
	& Create<"TextButton", Props.TextButton, TextButton>
	& Create<"TextBox", Props.TextBox, TextBox>
	& Create<"ImageLabel", Props.ImageLabel, ImageLabel>
	& Create<"ImageButton", Props.ImageButton, ImageButton>
	& Create<"VideoFrame", Props.VideoFrame, VideoFrame>
	& Create<"ViewportFrame", Props.ViewportFrame, ViewportFrame>
	& Create<"CanvasGroup", Props.CanvasGroup, CanvasGroup>
	& Create<"UIListLayout", Props.UIListLayout, UIListLayout>
	& Create<"UIGridLayout", Props.UIGridLayout, UIGridLayout>
	& Create<"UITableLayout", Props.UITableLayout, UITableLayout>
	& Create<"UIPageLayout", Props.UIPageLayout, UIPageLayout>
	& Create<"UIAspectRatioConstraint", Props.UIAspectRatioConstraint, UIAspectRatioConstraint>
	& Create<"UISizeConstraint", Props.UISizeConstraint, UISizeConstraint>
	& Create<"UITextSizeConstraint", Props.UITextSizeConstraint, UITextSizeConstraint>
	& Create<"UICorner", Props.UICorner, UICorner>
	& Create<"UIStroke", Props.UIStroke, UIStroke>
	& Create<"UIGradient", Props.UIGradient, UIGradient>
	& Create<"UIPadding", Props.UIPadding, UIPadding>
	& Create<"UIScale", Props.UIScale, UIScale>
	& Create<"ScreenGui", Props.ScreenGui, ScreenGui>
	& Create<"BillboardGui", Props.BillboardGui, BillboardGui>
	& Create<"SurfaceGui", Props.SurfaceGui, SurfaceGui>
	& Create<string, {[string | number]: any}, Instance>

local function parsePropertiesTable(inst: Instance, tbl: {[any]: any})
	local parent = tbl.Parent
	tbl.Parent = nil

	for k, v in tbl do
		if type(k) == "number" then
			if type(v) == "string" then
				inst:AddTag(v)
			elseif type(v) == "function" then
				v().Parent = inst
			elseif typeof(v) == "Instance" then
				v.Parent = inst
			end
		else
			inst[k] = v
		end
	end

	if parent then
		inst.Parent = parent
	end
end

local function createInstFromArgs(tbl: {[any]: any}, className: string)
	local inst = Instance.new(className)
	parsePropertiesTable(inst, tbl)
	return inst
end

local create: Create_Overloaded = function(className: string)
	return function(tbl: {[string | number]: any})
		return createInstFromArgs(tbl, className)
	end
end

return create


--!strict

local main = script.Parent

local shared = main.Shared

local PluginUI = require(shared.PluginUI)
local Style = require(shared.Style)
local GitAPI = require(shared.GitAPI)

local ItemGroup = require(shared.Vendor.ItemGroup)
local SimpleSignal = require(shared.Vendor.SimpleSignal)
local ColorPicker = require(shared.Vendor.PluginColorPicker)

local Jecs = require(main.Jecs)

-- UI definitions

export type UITEMPLATE_JExplorerUi = Frame & {
	InstanceTree: Frame & {
		TreeScroller: ScrollingFrame,
		ScrollContainer: Frame,

		ScrollBackgroundHorizontal: Frame,
		ScrollBackgroundVertical: Frame,
	},

	Topbar: Frame & {
		UIStroke: UIStroke,
		PathBar: Frame & {
			Segments: ScrollingFrame & {
				PathSegment: TextButton,
			}
		},
		TabBar: Frame & {
			Tabs: ScrollingFrame,
		},
		Options: ImageButton,
		QueryInputFrame: Frame & {
			Scroll: ScrollingFrame & {
				QueryInput: TextBox,
			},
			SelectQueried: TextButton,
			QueryCount: TextLabel,
		}
	}
}

type UITEMPLATE_InstanceFrame_Main = Frame & {
	Overlay: Frame & {
		Select: ImageButton,
	},
	ClassIcon: ImageLabel,
	InstanceName: TextBox,
	Arrow: ImageButton,
	Insert: ImageButton,

	PackageLinkIcon: ImageLabel,
	PendingChanges: ImageLabel,
	Reclass: ImageButton
}

export type UITEMPLATE_InstanceFrame = Frame & {
	Buffer: Frame,
	Ruler: Frame,
	Main: UITEMPLATE_InstanceFrame_Main,
}

export type UITEMPLATE_EditorFrame = Frame & {
	Items: ScrollingFrame & {
		Content: Frame & {
			UIListLayout: UIListLayout,
		},
		Overlay: Frame,
	},
	ScrollBackgroundHorizontal: Frame,
	ScrollBackgroundVertical: Frame,
}

export type UITEMPLATE_TabFrame = Frame & {
	NameBox: TextButton,
	Button: TextButton,
	Icon: ImageLabel,
}

type Array<T> = {T}
export type InstanceId = string
export type TempId = string
type UDFValue = string
type PlaceId = string

export type SerializedStyleRule = {
	display_name: string,
	selector: string,
	priority: number,
	props: {[string]: UDFValue},
	rules: {SerializedStyleRule},
	tokens: {[string]: SerializedStyleToken},
	tags: {string},
	attributes: {[string]: UDFValue}
}
export type SerializedStyleToken = {
	value: UDFValue,
	tags: {string}
}
export type SavedState = {
	repo_proxies: {
		[PlaceId]: Array<{
			display_name: string,
			repo_id: string,
			root_inst_id: InstanceId
		}>
	},
	tabs: {
		[PlaceId]: Array<{
			display_name: string,
			icon: string,
			icon_color: UDFValue,
			inst_ids: {InstanceId},
			layout_order: number
		}>
	},
	display_order: {
		default_order: number,
		class_order: {string}
	},
	data_version: number,
	invisible: {[string]: boolean},
	settings: {[string]: UDFValue},
	style: {
		rules: {SerializedStyleRule},
		tokens: {[string]: SerializedStyleToken}
	},
	icon_colors: {
		[PlaceId]: {[InstanceId]: UDFValue}
	},
	versions: {
		[PlaceId]: {
			[InstanceId]: {
				{
					timestamp: typeof(DateTime.now():ToUniversalTime()),
					props: {[string]: UDFValue},
				}
			}
		}
	},
}

export type VisibleViewportInfo = {
	visibleArray: Array<{
		entity: ExplorerEntity,
		yoffset: number,
		indentoffset: number
	}>,
	entityToIndex: {[ExplorerEntity]: number},
	entityToVisibleIndex: {[ExplorerEntity]: number},
	explorerFrameHeight: number,
	expandedHeightIndex: {[ExplorerEntity]: number},
	flatCount: number,
	visibleExpandedAncestors: Array<{
		stickyY: number?,
		rulerHeight: number
	}>,
	stickyStackDepth: number,
	viewportTop: number,
	visibleSet: {[ExplorerEntity]: boolean}
}
export type ItemGroup<T=any> = ItemGroup.ItemGroup<T>
type Signal<T... = ()> = SimpleSignal.SimpleSignal<T...>
export type DragInfo_SetInstTypeProperty = {
	-- Set instance type properties
	Type: "setproperty",
	TargetInstances: {Instance},
	PropertyName: string
}
export type DragInfo_ReparentInstances = {
	-- Drag into
	Type: "reparent",
	Entities: {ExplorerEntity},
}
export type StudioIconResult = {
	Image: string,
	ImageRectOffset: Vector2,
	ImageRectSize: Vector2
}

------------------------------------ Context Menu
export type MenuDef = {
	Id: string,
	Priority: number,
	Condition: (manager: Manager, target: ExplorerEntity) -> boolean,
	Contents: ((utils: ContextMenuUtils) -> {any}) | {any},
}
export type ActionDef = {
	Id: string?,
	Callback: (manager: Manager, target: ExplorerEntity) -> (),
	Text: string,
	Desc: string?,
	Icon: (() -> string)?
}

export type ContextMenuConfigModuleReturnValue =
|	ContextMenuConfigs
|	(Plugin, Manager, ContextMenuUtils, {any}) -> ContextMenuConfigs

export type ContextMenuConfigs = {
	Menus: {MenuDef},
	Actions: {[string]: ActionDef}
}

export type ContextMenuUtils = {
	createInterface: (defaultName: string) -> ContextMenuInterface,
	getMenuIds: () -> {string},
	createModifiedMenu: (menuId: string, newContents: (ContextMenuInterface) -> {any}) -> MenuDef,

	createSubMenu: (id: string, title: string, icon: any?, content: {any}) -> any,
	createSection: (sectionName: string) -> any,
}
export type ContextMenuInterface = {
	getMenuContents: () -> {any},
	getSubMenu: (name: string) -> ContextMenuInterface,

	remove: (idx: number) -> (),
	insert: (idx: number, v: any) -> (),
	inline: (idx: number, arr: {any}) -> (),

	findAction: (name: string) -> number,
	findSubMenu: (name: string) -> number,

	relocate: (name: string, newIndex: number) -> (),
	flipOrder: () -> (),

	getSection: (sectionName: string) -> (number, number),
	addSection: (idx: number, sectionName: string, content: {any}) -> (),
}
export type ContextMenu = {
	Menu: PluginMenu,
	SubMenus: {ContextMenu},
	Contents: {any},
	Id: string,
	Actions: {PluginAction},
	Sections: {[string]: number}, -- {[SectionName]: MaxIndex}

	ShowAsync: (self: ContextMenu, targetOverride: any?) -> (),
	Instantiate: (self: ContextMenu) -> ()
}

------------------------------------ end Context Menu
export type Tab = {
	TabFrame: GuiObject,
	Button: GuiButton,
	InsertButton: GuiButton?,
	RemoveButton: GuiButton?,
	NameBox: TextBox,
	IconLabel: ImageLabel,

	Roots: {ExplorerEntity},
	Name: string,
	Icon: string,
	IconColor: Color3,

	Toggle: () -> (),
	Remove: () -> (),
	QueryDescendants: (selector: string) -> {Instance}
}
export type RepoProxy = {
	DisplayName: string,
	RepoId: string,
	Root: Instance,

	Remove: () -> ()
}
--export type InstanceFrame = {
--	Instance: typeof(ui.InstanceFrame),
--	Connections: Group.Group,
--	CurrentEntity: ExplorerEntity?,
--	TagsToRemove: {string},

--	Assign: (InstanceFrame, entity: ExplorerEntity?, parent: ExplorerEntity?) -> (),
--	ToggleSelection: (InstanceFrame, selected: boolean) -> (),
--	Reuse: (InstanceFrame) -> ()
--}
export type ExplorerFrame = Jecs.Id
export type PropertyItem = {
	name: string,
	type: ReflectionType,
	writable: boolean,
	readable: boolean
}

export type World = Jecs.World
export type ExplorerEntity = Jecs.Id
export type QueryEntity = Jecs.Id
export type FrameState = {
	isLMBActive: boolean,
	tagsToRemove: {string},
	hasBeenSelected: boolean,
	shouldDeselect: boolean,
	lastClickTime: number,
	lastPackageIconPos: number,
	isAssigning: boolean,
	setIconColor: Color3?,
	icon: ImageLabel,
	packageLinkIcon: ImageLabel,
	isVisible: boolean,
	instanceName: TextBox,
	pendingChanges: ImageLabel,
	arrow: GuiButton,
	hasPackageLink: boolean,
	insertInstanceButton: GuiButton,
	reclassButton: GuiButton,
	currentFrameName: string?,
	packageLinkIconVisible: boolean,
	pendingChangesIconVisible: boolean,
	reclassIconVisible: boolean,
	currentNameBoundX: number,
	currentFrameX: number,
	lastFrameX: number,
	overlayFrame: Frame,
	isSticky: boolean,

	linkIconPos: UDim2?,
	pendingChangesIconPos: UDim2?,
	reclassIconPos: UDim2?
}

export type Components = {
	Selected: Jecs.Component,
	Expanded: Jecs.Component,
	AncestryLocked: Jecs.Component,
	ArrowInvisible: Jecs.Component,
	QueryAncestor: Jecs.Component,
	QueryExpanded: Jecs.Component,
	QueryInvisible: Jecs.Component,
	Invisible: Jecs.Component,
	IsQueried: Jecs.Component,
	PendingChanges: Jecs.Component,
	Deleted: Jecs.Component,
	DisplayTopLevel: Jecs.Component,
	HasPackageLink: Jecs.Component,
	HasSideIcons: Jecs.Component,

	CleanupTicket: Jecs.Component<{}>,
	DeletionTicket: Jecs.Component<{}>,
	ChildAddedTicket: Jecs.Component<{children: {Instance}}>,
	ChildMovedTicket: Jecs.Component<{children: {Instance}}>,
	CreationTicket: Jecs.Component<{children: {Instance}}>,
	PropAssignTicket: Jecs.Component<{ty: "virtual" | "instance"}>,

	FrameReuseTicket: Jecs.Component<{frame: ExplorerFrame}>,
	FrameTagAssignTicket: Jecs.Component<{frame: ExplorerFrame}>,

	FRAMEWORLD_FrameSetParentTicket: Jecs.Component<{newParent: Instance?}>,
	FRAMEWORLD_Main: Jecs.Component<UITEMPLATE_InstanceFrame_Main>,
	FRAMEWORLD_CurrentParent: Jecs.Component<Instance>,
	FRAMEWORLD_Ruler: Jecs.Component<GuiObject>,
	FRAMEWORLD_IsSticky: Jecs.Component,
	QUERYWORLD_ComponentHash: Jecs.Component<string>,

	Instance: Jecs.Component<Instance>,
	Frame: Jecs.Component<ExplorerFrame>,
	Entity: Jecs.Component<ExplorerEntity>,
	FrameState: Jecs.Component<FrameState>,
	FrameIconColor: Jecs.Component<Color3>,

	Connections: Jecs.Component<ItemGroup<RBXScriptConnection>>,
	ScriptErrors: Jecs.Component<{string}>,
	ScriptWarns: Jecs.Component<{string}>,
	QueryLinkedEntities: Jecs.Component<{ExplorerEntity}>,
	LinkedQueryEntity: Jecs.Component<QueryEntity>,
	ParentTabs: Jecs.Component<{[Tab]: boolean}>,
}

export type ContextMenuInfo = {
	Menu: ContextMenu,
	Condition: (manager: Manager, target: ExplorerEntity, instance: Instance) -> boolean,
	Priority: number
}

export type WidgetState = {
	World: World,
	QueryWorld: World,
	FrameWorld: World,
	InstanceTree: GuiObject,
	TreeScroller: ScrollingFrame,

	Connections: ItemGroup<RBXScriptConnection>,
	Entities: {ExplorerEntity},
	Widget: DockWidgetPluginGui,
	CurrentSelection: {ExplorerEntity},
	Favorited: {ExplorerEntity},
	Tabs: {Tab},
	RepoProxies: {RepoProxy},
	SavedState: SavedState,

	LastSelectedEntity: ExplorerEntity?,
	ContextTarget: ExplorerEntity?,

	CurrentlyRenaming: ExplorerEntity?,
	CurrentlyInserting: {Instance}?,
	IsDragging: boolean,

	JExplorerUI: UITEMPLATE_JExplorerUi,
	DefaultInstanceActionsArray: {any},
	InstanceContextMenus: {[string]: ContextMenuInfo},
	MiscContextMenus: {[string]: ContextMenu},
	OverridePathBarText: string?,
	Enabled: boolean
}

export type HierarchyState = {
	StateCache: {[ExplorerEntity]: {invisible: boolean, expanded: boolean, isTopLevel: boolean}},
	ChildrenMap: {[ExplorerEntity]: {ExplorerEntity}},

	cleanupEntities: (Manager) -> boolean,
	cleanupEntity: (Manager, ExplorerEntity) -> (),
	rebuildHierarchyCache: (Manager) -> ({[ExplorerEntity]: {ExplorerEntity}}, {ExplorerEntity}),
	getFlattenedVisibleEntities: (Manager, invisible: {ExplorerEntity}, isScroller: boolean) -> ({ExplorerEntity}, {[ExplorerEntity]: number}, {number}),

	invalidateEntityStateCache: (ExplorerEntity) -> (),

	flushEntityCreationQueue: (Manager) -> boolean,
	queueForEntityCreation: (Manager, parent: ExplorerEntity, children: {Instance}) -> (),

	flushEntityCleanupQueue: (Manager) -> boolean,
	queueForEntityCleanup: (Manager, entity: ExplorerEntity) -> (),

	flushOnChildAddedQueue: (Manager) -> boolean,
	queueForOnChildAdded: (Manager, new_parent: ExplorerEntity, child: Instance) -> (),

	flushOnChildRemovedQueue: (Manager) -> boolean,
	queueForOnChildRemoved: (Manager, old_parent: ExplorerEntity, child: Instance) -> (),

	stepFlushEntityDeletionQueue: (Manager) -> boolean,
	queueForEntityDeletion: (Manager, entity: ExplorerEntity) -> (),

	getEntitiesBetween: (Manager, e1: ExplorerEntity, e2: ExplorerEntity) -> {ExplorerEntity},

	flushEntityPropAssignQueue: (Manager) -> (),

	getOrCreateInstanceEntity: (Manager, instance: Instance, parent: ExplorerEntity?, dontSetInInstanceToEntity: boolean?) -> ExplorerEntity,
	createVirtualEntity: (Manager, options: {
		ClassName: string,
		Name: string,
		ChildAmount: number,
		ClassIcon: StudioIconResult,
		PropTags: {Jecs.Id},
		Parent: ExplorerEntity?
	}, extra: {[string]: any}) -> ExplorerEntity,
	createInstanceEntity: (Manager, instance: Instance, parent: ExplorerEntity?, dontSetInInstanceToEntity: boolean?) -> ExplorerEntity,

	deregisterFromQueryWorld: (Manager, entity: ExplorerEntity) -> (),
	registerIntoQueryWorld: (Manager, entity: ExplorerEntity, className: string, tags: {Jecs.Id}, attributes: {Jecs.Id}) -> (),

	updateEntityOrder: (Manager, entity: ExplorerEntity) -> (),
	getChildren: (ExplorerEntity) -> {ExplorerEntity},
}

export type ExplorerFrameLib = {
	fillGlobalPoolToLimit: (Manager) -> (),
	--fillLocalPoolToLimit: (Manager) -> (),
	initializeFrameCaches: (Manager) -> (),

	getExplorerFrame: (Manager) -> ExplorerFrame,
	assignExplorerFrame: (Manager, ExplorerFrame, assignee: ExplorerEntity?, parent: ExplorerEntity?, parentIsGame: boolean, assignSetParentTicket: boolean) -> boolean?,
	flushExplorerFrameTagAssignQueue: (Manager) -> boolean,

	queueForExplorerFrameReuse: (Manager, ExplorerFrame, ExplorerEntity?) -> (),
	flushFrameReuseQueue: (Manager) -> boolean,
	flushFrameSetParentQueue: (Manager) -> boolean,

	updateSelectionVisuals: (Manager, updateSelection: boolean) -> (),
	updateExpandedVisuals: (Manager) -> (),
	updateCollapsedVisuals: (Manager) -> (),

	updateRightEdgeIcons: (Manager, frame: ExplorerFrame, containerWidth: number?, frameX: number?, entity: ExplorerEntity?, frameState: FrameState?) -> (),
	setExplorerFrameIconColor: (Manager, frame: ExplorerFrame, iconColor: Color3?) -> (),
}

export type VirtualScroller = {
	clearHeightCache: (Manager) -> (),
	invalidateEntityHeightCache: (Manager, ExplorerEntity) -> (),

	getVisibleViewportInfo: (Manager, top: number, isScroller: boolean) -> VisibleViewportInfo,
	getLastVisibleViewportInfo: (Manager) -> VisibleViewportInfo?,
	update: (Manager, isScroller: boolean, forceUpdateIcons: boolean?) -> VisibleViewportInfo?,
	rebuildAndUpdate: (Manager, isScroller: boolean) -> (),
	trackScrolling: (Manager) -> (RBXScriptConnection, thread),

	IsScrolling: boolean,
	IsVirtualScrollerUpdating: boolean
}

export type buttonItem = {
	button: Frame,
	className: string,
	category: string
}
export type ClassSelectionMenuInfo = {
	currentOnSelectCallback: ((class: string) -> ())?,
	currentInsertTarget: number,
	classSelectList: PluginUI.ItemList,
	currentVisibleButtons: {buttonItem},
	searchbar: TextBox,
	highlightFrame: Frame,
	setScope: (scope: string) -> (),
	setScopeInfo: (scope: string, frequentClasses: {string}) -> ()
}
export type ClassSelectionMenu = {
	init: (Manager) -> (),
	show: (Manager, buttonAbsolutePos: Vector2, buttonAbsoluteSize: Vector2, onSelectClass: (class: string) -> ()) -> (),
	menuInfo: ClassSelectionMenuInfo
}

export type DeferredTaskScheduler = {
	ChildGraphDirty: boolean,

	FrameReuseQueuePending: boolean,
	FrameTagAssignQueuePending: boolean,
	EntityCleanupQueuePending: boolean,
	EntityCreationQueuePending: boolean,
	EntityChildAddedQueuePending: boolean,
	EntityChildRemovedQueuePending: boolean,
	EntityPropAssignQueuePending: boolean,
}

export type SharedTabState = {
	currentlyPickingTabRoots: Tab?,
	tabInsertListener: RBXScriptConnection?,
	currentlyOpenTab: Tab?,
	contextTargetTab: Tab?
}

export type ActionInfos = {[string]: {
	Id: string,
	Text: string,
	Desc: string?,
	Icon: (() -> string)?,
	Triggered: ((target: any) -> ())?
}}

export type Manager = {
	CleanupFns: typeof(ItemGroup.cleanup),
	WidgetState: WidgetState,
	TabState: SharedTabState,
	Components: Components,
	Toolbar: PluginToolbar,
	Storage: {
		repoRoots: Instance,
		idObjects: Instance,
		publicTypedef: ModuleScript,
		self: Instance
	},

	CurrentTheme: string,

	TabChanged: Signal<Tab, Tab?>,
	SelectionChanged: Signal<{ExplorerEntity}>,

	InstTreeAbsoluteSizeY: number,
	InstTreeAbsoluteSizeX: number,

	QueryTagMap: {[string]: Jecs.Id},
	QueryCtMap: {[string]: Jecs.Id},
	QueryAttrMap: {[string]: Jecs.Id},
	AttributeUsageAmounts: {[string]: number},

	Script: Instance,

	ExplorerFrame: ExplorerFrameLib,
	HierarchyState: HierarchyState,
	VirtualScroller: VirtualScroller,
	ClassSelectionMenu: ClassSelectionMenu,
	DeferredTaskScheduler: DeferredTaskScheduler,

	ActionInfos: ActionInfos,
	BindableActions: {[string]: PluginAction},

	ItemLists: {
		InsertInstance: PluginUI.ItemList
	},
	Style: Style.Style,
	AllTab: Tab,

	InstanceToEntity: {[Instance]: ExplorerEntity},
	FrameToInstanceFrame: {[Instance]: ExplorerFrame},
	IconColorMap: {[InstanceId]: Color3},

	ChildAmountCache: {[ExplorerEntity]: number},
	ClassNameCache: {[ExplorerEntity]: string},
	NameCache: {[ExplorerEntity]: string},

	IdToInstance: {[InstanceId]: Instance},
	InstanceToId: {[Instance]: InstanceId},
	IdToObjectValue: {[InstanceId]: ObjectValue},

	TempIdToInstance: {[TempId]: Instance},
	InstanceToTempId: {[Instance]: TempId},

	TabFrameToTab: {[Instance]: Tab},

	createPluginAction: (actionId: string, text: string, statusTip: string, iconId: string?) -> PluginAction,
	takeContextTarget: () -> ExplorerEntity,

	connectInputBegan: (fn: (input: InputObject) -> ()) -> RBXScriptConnection,
	connectInputEnded: (fn: (input: InputObject) -> ()) -> RBXScriptConnection,
	isKeyDown: (key: Enum.KeyCode) -> (boolean),
	isInputActive: (input: Enum.UserInputType) -> (boolean),

	registerTab: (name: string, icon: string, roots: {ExplorerEntity}, registerIntoTabsArray: boolean) -> Tab,

	getPropertyTag: (world: World, propName: string) -> Jecs.Id,

	triggerDuplicateTarget: () -> (),
	triggerDuplicateTargetWithIncrementName: () -> (),
	triggerDeleteTarget: () -> (),
	triggerEscapeTarget: () -> (),

	createContextMenu: (id: string, contents: {any}, title: string?, icon: (() -> string)?) -> ContextMenu,
	registerRightClickContextMenu: (menuId: string, menu: ContextMenu, condition: (manager: Manager, target: ExplorerEntity, inst: Instance) -> boolean, priority: number) -> (),

	getEntityAt: (x: number, y: number, filteredParent: Instance?) -> (Instance?, ExplorerEntity?, Tab?),

	onTagAdded: (inst: Instance, callback: (tag: string) -> ()) -> RBXScriptConnection,
	onTagRemoved: (inst: Instance, callback: (tag: string) -> ()) -> RBXScriptConnection,

	getOrCreateInstId: (instance: Instance) -> string,
	getInstId: (instance: Instance) -> string?,
	getInstFromId: (id: string) -> Instance?,

	getTempId: (instance: Instance) -> string,
	getInstFromTempId: (id: string) -> Instance,

	getSetting: (name: string) -> any,
	setSetting: (name: string, value: any) -> (),

	isExplorerEntityInvisible: (entity: ExplorerEntity) -> boolean,
	isExplorerEntityDeleted: (entity: ExplorerEntity) -> boolean,
	isExplorerEntityExpanded: (entity: ExplorerEntity) -> boolean,

	getPropertyComponentsForClass: (World, class: string) -> {Jecs.Id},

	isInstanceExcluded: (inst: Instance) -> boolean,

	getSetPropertyDragInfo: (instances: {Instance}, propName: string) -> any,
	getDragEntitiesDragInfo: (entities: {ExplorerEntity}) -> any,
	unpackDragInfo: (dragInfo: any) -> DragInfo_ReparentInstances | DragInfo_SetInstTypeProperty,

	addExplorerEntitiesToSelection: (entities: {ExplorerEntity}, updateSelection: boolean) -> (),
	setExplorerEntitiesAsSelection: (entities: {ExplorerEntity}, updateSelection: boolean) -> (),

	refreshQueryTags: (entity: ExplorerEntity, tagEnt: Jecs.Id?) -> (),
	refreshQueryAttributes: (entity: ExplorerEntity, attrEnt: Jecs.Id?) -> (),

	startDraggingSelected: () -> (),

	ChildOf: (ent: ExplorerEntity) -> Jecs.Pair<Jecs.Id, ExplorerEntity>,

	init: (connections: ItemGroup<RBXScriptConnection>, widget: DockWidgetPluginGui) -> (),

	frameOf: (frame: ExplorerFrame) -> UITEMPLATE_InstanceFrame?,
	parentOf: (entity: ExplorerEntity) -> ExplorerEntity?,
	instanceOf: (entity: ExplorerEntity) -> Instance?,
	updateParent: (entity: ExplorerEntity, newParent: ExplorerEntity?) -> (),

	convertEntitiesInto: (entities: {ExplorerEntity}, class: string) -> {ExplorerEntity},
	insertClassInto: (entity: ExplorerEntity, class: string) -> Instance?,

	jumpCanvasTo: (x: number?, y: number?, epsilon: number?) -> (),

	saveState: () -> {
		save: () -> (),
		all: () -> (),
		icon_colors: () -> (),
		style: () -> (),
		versions: () -> (),
		tabs: () -> (),
		repo_proxies: () -> (),
		settings: () -> (),
		order: () -> (),
		invisible: () -> (),
	},
	loadState: (state: SavedState) -> ({
		icon_colors: () -> (),
		style: () -> Style.Style,
		versions: () -> {
			[Instance]: {
				{
					timestamp: DateTime,
					props: {[string]: any},
				}
			}
		},
		tabs: () -> (),
		repo_proxies: () -> (),
		settings: () -> (),
		order: () -> (),
		invisible: () -> (),
	}, SavedState)
}

type Editor = {
	Widget: DockWidgetPluginGui,

	init: (Manager) -> (),
}

export type StyleEditor = Editor & {

}

export type SettingEditor = Editor & {
	Configs: {[string]: any},

	getSetting: (Manager: Manager, name: string) -> any,
	setSetting: (Manager: Manager, name: string, value: any) -> (),
	createSetting: (list: PluginUI.ItemList, settingName: string, displayName: string, valueType: string, defaultValue: any, onChange: ((newValue: any) -> ())?) -> (),
}

export type OrderEditor = Editor & {
	ClassPriorities: {string},
	DefaultPriority: number,
	PriorityVersion: number
}

export type VersionControl = Editor & {
	InstanceHistoryWidget: DockWidgetPluginGui,
	InstanceHistoryUIList: PluginUI.ItemList,

	VersionViewerWidget: DockWidgetPluginGui,
	VersionViewerUIList: PluginUI.ItemList,

	SourceViewerWidget: DockWidgetPluginGui,
	SourceViewerMainframe: UITEMPLATE_EditorFrame,

	Token: GitAPI.LuaSecret?,
	RepoId: string,
	Versions: {
		[Instance]: {
			{
				timestamp: DateTime,
				props: {[string]: any},
			}
		}
	},
	PendingChanges: {
		[Instance]: {
			changedProps: {[string]: any},
			originalProps: {[string]: any}
		}
	},

	openSourceViewer: (src: string) -> (),
	showInstanceHistoryWidget: (inst: Instance) -> (),
	createInstanceVersion: (inst: Instance, changedProp: string, newValue: any) -> boolean,
	appendInstanceVersion: (inst: Instance, changedProp: string, newValue: any) -> boolean,

	addPendingChange: (inst: Instance, changedProp: string, newValue: any) -> (),
	appendPendingChanges: (inst: Instance) -> boolean,
	createPendingChanges: (inst: Instance) -> boolean,
	discardPendingChanges: (inst: Instance) -> boolean,

	createRepoProxy: (display_name: string, repo_id: string, root: Instance, register_into_proxies_array: boolean) -> RepoProxy
}

export type InstanceVisibilityEditor = Editor & {
	InvisibleClasses: {[string]: boolean},
	InvisibleInstances: {[Instance]: boolean},

	setInvisible: (className: string, invisible: boolean) -> boolean,
}

export type PropertyEditor = Editor & {
	PropertyList: PluginUI.ItemList,

	CurrentSetNilTarget: {instances: {Instance}, name: string}?,
	SetNilMenu: PluginMenu,

	updateProperties: (instances: {Instance}) -> (),
	clearFramePools: () -> ()
}

export type ContextMenuEditor = Editor & {

}

export type JextensionEditor = Editor & {

}

export type StateEditor = Editor & {

}

export type ExplorerFrameEditor = Editor & {

}

export type Jexplorer = {
	Manager: Manager,
	StyleEditor: StyleEditor,
	SettingEditor: SettingEditor,
	OrderEditor: OrderEditor,
	VersionControl: VersionControl,
	PropertyEditor: PropertyEditor,

	Dependencies: {[string]: any},

	OnLoad: RBXScriptSignal,
	OnClose: RBXScriptSignal,
	ModifyIconStep: RBXScriptSignal
}

return nil


--!strict
--!optimize 2
--version 0.0.0

--[[
	UniPlugin: Plugins, unified.

	Author: athar_adv
]]
	--[=[
	@type Api {[string]: any}
	@within UniPlugin
	A publicly exported interface for the UniPlugin.
]=]
--[=[
	@class UniPluginConnection
	A connection that can be closed.
]=]
--[=[
	@prop isClosed boolean
	@within UniPluginConnection
	Indicates wether or not `UniPluginConnection:close()` has been called or not.
]=]
--[=[
	@method close
	@within UniPluginConnection
	Closes this `UniPluginConnection`. Sets `UniPluginConnection.isClosed` to true.
]=]

--[=[
	Wraps a Roblox `Plugin` instance to provide a shared
	export/import system between plugins.

	@class UniPlugin
]=]
--[=[
	@prop plugin Plugin
	@within UniPlugin
	The Plugin this UniPlugin is associated with.
]=]
--[=[
	@prop apis {[string]: Api}
	@within UniPlugin
	All registered public apis.
]=]
--[=[
	@prop isClosed boolean
	@within UniPlugin
	If `UniPlugin:close()` has been called. If this is true, calling any method will throw an error.
]=]

export type Api = {[string]: any}
export type UniPluginConnection = {
	close: (UniPluginConnection) -> (),
	isClosed: boolean
}

local exports: {[string]: UniPlugin} = shared

local MAIN_API_ALIAS = "main"

if not shared.__UniPluginExportOpened then
	shared.__UniPluginExportOpened = Instance.new("BindableEvent")
end
local ExportOpened: BindableEvent = shared.__UniPluginExportOpened

local UniPlugin = {}

type Impl = {
	plugin: Plugin,
	apis: {[string]: Api},
	onCloseListeners: {() -> ()},
	connectionsToClose: {UniPluginConnection},
	isClosed: boolean,
	closerConnections: {RBXScriptConnection}
}
export type UniPlugin = typeof(UniPlugin) & Impl
export type ImportedUniPlugins = () -> ...UniPlugin

local function findAndRemove<V>(tbl: {[any]: V}, v: V)
	local idx = table.find(tbl, v)
	if idx then table.remove(tbl, idx) end
end

local function checkIsClosed(self: UniPlugin, methodName: string)
	if self.isClosed then
		error(`UniPlugin '{self.plugin.Name}' is closed, cannot call UniPlugin:{methodName}(...)`, 0)
	end
end

--[=[
	Registers this UniPlugin under `alias` in the shared exports table, making
	it discoverable by other plugins calling `withImports`. Fires the global
	ExportOpened event so any waiting `withImports` listeners are unblocked.

	The returned listener's `close` removes the export and can be called
	manually; it is also called automatically when the plugin unloads or is
	destroyed.

	@within UniPlugin
	@param alias string -- The name to register this plugin under
	@return UniPluginConnection -- A connection that can be closed to unregister the export
	@error "UniPlugin '{self.plugin.Name}' is closed, cannot call UniPlugin:exportAs(...)" -- Occurs if UniPlugin.isClosed is true.

	**Example Usage:**
	```lua
	local UniPlugin = require(path.to.module)(plugin)

	UniPlugin:setApi("output", {
		print = print,
		warn = warn,
		error = error
	})
	UniPlugin:setApi("requireInMyContext", {
		execute = function(module) return require(module) end
	})
	UniPlugin:exportAs("athar-adv/MyPlugin")
]=]
function UniPlugin.exportAs(self: UniPlugin, alias: string): UniPluginConnection
	checkIsClosed(self, "exportAs")

	exports[alias] = self
	ExportOpened:Fire(alias)

	local onClose
	local conn = {
		close = function(conn: UniPluginConnection)
			if conn.isClosed then return end
			conn.isClosed = true

			findAndRemove(self.connectionsToClose, conn)
			onClose:close()
			exports[alias] = nil
		end,
		isClosed = false
	}
	onClose = self:onClose(function()
		conn:close()
	end)
	table.insert(self.connectionsToClose, conn)
	return conn
end

--[=[
	Waits for all plugins in `aliases` to be exported, then calls `handler`
	with two arguments:
	- `plugins()` — returns all resolved UniPlugins in the same order as `aliases` via multiple returns.
	- `bindOnImportClosed(fn)` — registers a single callback (overwriting any previous one) that fires whenever this import group is torn down, whether because one of the imported plugins closed naturally or because the returned listener was closed manually.

	The handler re-runs automatically each time all aliases become available
	again after a close. Duplicate aliases are silently deduplicated. Passing
	an empty `aliases` table is a no-op and returns an inert connection.

	Close the returned connection to stop listening entirely and tear down the
	current import group.

	@within UniPlugin
	@param aliases {string} -- List of plugin export aliases to wait for
	@param handler (plugins: () -> ...UniPlugin, bindOnImportClosed: (fn: () -> ()) -> ()) -> () -- Called once all aliases are available; re-runs if the group is torn down and re-established
	@return UniPluginConnection -- A connection that can be closed to stop tracking imports
	@error "UniPlugin '{self.plugin.Name}' is closed, cannot call UniPlugin:withImports(...)" -- Occurs if UniPlugin.isClosed is true.

	**Example Usage:**
	```lua
	local UniPlugin = require(path.to.module)(plugin)

	UniPlugin:withImports({"athar-adv/Jexplorer", "athar-adv/SimpleComplete"}, function(plugins, onImportClosed)
		local jexplorer, simpleComplete = plugins()

		local items = {}
		do
			local manager = jexplorer:getApi("manager")
			manager.setSetting("VirtualScrollFrameBuffer", 20)

			table.insert(items, manager.ExplorerFrame.ClassIconStyleRuleCreated:Connect(function(rule)
				rule:SetProperty("Image", "rbxassetid://1234")
			end))
		end
		do
			local selectionImports = simpleComplete:getApi("selectionImports")

			selectionImports.triggerAtCurrentPosition()
		end
		onImportClosed(function()
			for _, v in items do
				v:Disconnect()
			end
		end)
	end)
]=]
function UniPlugin.withImports(self: UniPlugin, aliases: {string}, handler: (plugins: ImportedUniPlugins, bindOnImportClosed: (fn: () -> ()) -> ()) -> ()): UniPluginConnection
	checkIsClosed(self, "withImports")

	if #aliases == 0 then
		return {
			close = function(conn) conn.isClosed = true end,
			isClosed = false
		}
	end

	local seen: {[string]: boolean} = {}
	local dedupedAliases: {string} = {}
	for _, alias in aliases do
		if not seen[alias] then
			seen[alias] = true
			table.insert(dedupedAliases, alias)
		end
	end

	local onCloses: {UniPluginConnection} = {}
	local generation = 0
	local onImportClosed: (() -> ())?

	local conn
	local function onPlugins(unips: {[string]: UniPlugin})
		local currentGen = generation
		local function unipClosed()
			if generation ~= currentGen then return end
			generation += 1
			for _, oc in onCloses do oc:close() end
			table.clear(onCloses)
			local cb = onImportClosed
			onImportClosed = nil
			if cb then cb() end
			conn:close()
			self:withImports(aliases, handler)
		end
		for _, unip in unips do
			local onClose = unip:onClose(unipClosed)
			table.insert(onCloses, onClose)
		end
		local ordered: {UniPlugin} = {}
		for _, alias in dedupedAliases do
			table.insert(ordered, unips[alias])
		end
		handler(
			function()
				return table.unpack(ordered)
			end,
			function(fn)
				onImportClosed = fn
			end
		)
	end

	local listenerConn: RBXScriptConnection
	local t = task.spawn(function()
		local resolved: {[string]: UniPlugin} = {}
		local unresolved: {[string]: boolean} = {}

		for _, alias in dedupedAliases do
			if exports[alias] then
				resolved[alias] = exports[alias]
			else
				unresolved[alias] = true
			end
		end

		if next(unresolved) then
			listenerConn = ExportOpened.Event:Connect(function(alias: string)
				if not unresolved[alias] then return end
				resolved[alias] = exports[alias]
				unresolved[alias] = nil
				if next(unresolved) == nil then
					listenerConn:Disconnect()
					onPlugins(resolved)
				end
			end)

			for alias in unresolved do
				if exports[alias] then
					resolved[alias] = exports[alias]
					unresolved[alias] = nil
				end
			end
			if next(unresolved) == nil then
				listenerConn:Disconnect()
				onPlugins(resolved)
			end
		else
			onPlugins(resolved)
		end
	end)

	conn = {
		close = function(conn: UniPluginConnection)
			if conn.isClosed then return end
			conn.isClosed = true
			findAndRemove(self.connectionsToClose, conn)

			generation += 1
			if listenerConn then listenerConn:Disconnect() end
			for _, oc in onCloses do oc:close() end
			table.clear(onCloses)
			local cb = onImportClosed
			onImportClosed = nil
			if cb then cb() end
			if coroutine.status(t) ~= "dead" then
				task.cancel(t)
			end
		end,
		isClosed = false
	}
	table.insert(self.connectionsToClose, conn)

	return conn
end

--[=[
	Registers `fn` to be called when this plugin is torn down (i.e. when the
	plugin unloads or is destroyed). Multiple listeners can be registered and
	all will fire on close.

	The returned listener's `close` unregisters `fn` and can be used to cancel
	the subscription before the plugin closes.

	@within UniPlugin
	@param fn () -> () -- Callback to invoke on plugin teardown
	@return UniPluginConnection -- A connection that can be closed to cancel the subscription
	@error "UniPlugin '{self.plugin.Name}' is closed, cannot call UniPlugin:onClose(...)" -- Occurs if UniPlugin.isClosed is true.

	**Example Usage:**
	```lua
	local UniPlugin = require(path.to.module)(plugin)

	UniPlugin:onClose(function()
		print("Whyd you close my plugin :(")
	end)
]=]
function UniPlugin.onClose(self: UniPlugin, fn: () -> ()): UniPluginConnection
	checkIsClosed(self, "onClose")

	table.insert(self.onCloseListeners, fn)
	local conn = {
		close = function(conn: UniPluginConnection)
			if conn.isClosed then return end
			conn.isClosed = true
			findAndRemove(self.onCloseListeners, fn)
			findAndRemove(self.connectionsToClose, conn)
		end,
		isClosed = false
	}
	table.insert(self.connectionsToClose, conn)
	return conn
end

--[=[
	Stores `api` under `alias` (defaults to `"main"`) on this UniPlugin.
	Retrieve it from another plugin via `getApi`.

	@within UniPlugin
	@param alias string? -- The alias to store the API under. Defaults to `"main"`
	@param api Api -- The API table to store
	@error "UniPlugin '{self.plugin.Name}' is closed, cannot call UniPlugin:setApi(...)" -- Occurs if UniPlugin.isClosed is true.

	**Example Usage:**
	```lua
	local UniPlugin = require(path.to.module)(plugin)

	UniPlugin:setApi("output", {
		print = print,
		warn = warn,
		error = error
	})
	UniPlugin:setApi("requireInMyContext", {
		execute = function(module) return require(module) end
	})
	UniPlugin:exportAs("athar-adv/MyPlugin")
]=]
function UniPlugin.setApi(self: UniPlugin, alias: string?, api: Api): ()
	checkIsClosed(self, "setApi")

	local alias = alias or MAIN_API_ALIAS
	self.apis[alias] = api
end

--[=[
	Returns the Api stored under `alias` (defaults to `"main"`).
	Errors if no Api has been set for that alias.

	@within UniPlugin
	@param alias string? -- The alias to look up. Defaults to `"main"`
	@return Api -- The stored API table
	@error "UniPlugin '{self.plugin.Name}' is closed, cannot call UniPlugin:getApi(...)" -- Occurs if UniPlugin.isClosed is true.

	**Example Usage:**
	```lua
	local UniPlugin = require(path.to.module)(plugin)

	UniPlugin:withImports({"athar-adv/Jexplorer", "athar-adv/SimpleComplete"}, function(plugins, onImportClosed)
		local jexplorer, simpleComplete = plugins()

		local items = {}
		do
			local manager = jexplorer:getApi("manager")
			print(manager.executeJqlQuery("#?player"))

			local dependencies = jexplorer:getApi("dependencies")
			print(dependencies.GitApi)
		end
		do
			local editor = simpleComplete:getApi("editor")
			print(editor.getOpenDocValue().currentImportedModules)
		end
	end)
]=]
function UniPlugin.getApi(self: UniPlugin, alias: string?): Api
	checkIsClosed(self, "getApi")

	local alias = alias or MAIN_API_ALIAS
	return assert(self.apis[alias], `Could not find Api '{alias}' inside UniPlugin '{self.plugin.Name}'`)
end

--[=[
	Closes this UniPlugin. Cleans up any connections, fires `onClose` handlers,
	and makes all methods error on subsequent calls.

	@within UniPlugin

	**Example Usage:**
	```lua
	local UniPlugin = require(path.to.module)(plugin)

	--[[snip!]]

	closePluginButton.Activated:Connect(function()
		UniPlugin:close()
	end)

	UniPlugin:onClose(function()
		for _, v in connections do v:Disconnect() end
	end)
]=]
function UniPlugin.close(self: UniPlugin)
	if self.isClosed then return end
	self.isClosed = true
	for _, v in self.closerConnections do
		v:Disconnect()
	end
	for _, v in table.clone(self.onCloseListeners) do
		v()
	end
	for _, v in table.clone(self.connectionsToClose) do
		v:close()
	end
	table.clear(self.closerConnections)
	table.clear(self.onCloseListeners)
	table.clear(self.connectionsToClose)
end

local created = {}

--[=[
	Returns the UniPlugin instance for the given `plugin`, creating it if one
	doesn't already exist. Only one UniPlugin is ever created per Plugin
	instance for the lifetime of the studio session.

	Automatically connects to `plugin.Unloading` and `plugin.Destroying` to
	fire all `onClose` listeners and close all active `withImports` and
	`exportAs` listeners when the plugin is torn down.

	@within UniPlugin
	@param plugin Plugin -- The Roblox Plugin instance to wrap
	@return UniPlugin
]=]
local function openUniPlugin(plugin: Plugin): UniPlugin
	local existing = created[plugin]
	if existing then return existing end

	local uniPlugin = {
		plugin = plugin,
		apis = {},
		onCloseListeners = {},
		connectionsToClose = {},
		isClosed = false,

		exportAs = UniPlugin.exportAs,
		withImports = UniPlugin.withImports,
		onClose = UniPlugin.onClose,
		close = UniPlugin.close,

		setApi = UniPlugin.setApi,
		getApi = UniPlugin.getApi
	} :: UniPlugin
	created[plugin] = uniPlugin

	uniPlugin.closerConnections = {
		plugin.Destroying:Connect(function()
			uniPlugin:close()
		end),
		plugin.Unloading:Connect(function()
			uniPlugin:close()
		end)
	}

	return uniPlugin
end

return openUniPlugin


--!strict
--!optimize 2

local plugin = script:FindFirstAncestorOfClass("Plugin")

local main = script.Parent.Parent

local utilities = main.Utilities
local ui = main.UI

local Types = require(main.Types)

local Editor = {} -- :: Types.Editor

function Editor.init(Manager)

end

return Editor


--!strict
--!optimize 2
--[[
	JExplorer
	This plugin is distributed under the MIT license (2026).

	Author: athar_adv
]]

if not plugin then return end

local CoreGui = game:GetService("CoreGui")

local VERSION = "5.6.0"

plugin.Name = `cloud/plugin/Jexplorer@{VERSION}`

local global = shared
local shared = script.Shared
local editors = script.Editors
--local ui = script.UI

local Jecs = require(script.Jecs)
local Manager = require(script.Manager)

local PluginUI = require(shared.PluginUI)
local ItemGroup = require(shared.Vendor.ItemGroup)
local SavedState = require(shared.SavedState)
local Types = require(script.Types)
local UniPlugin = require(script.UniPlugin)(plugin)
local cleanup = ItemGroup.cleanup

local running = coroutine.running()
local connections = ItemGroup.create(cleanup.disconnect)
local instances = connections:extend(cleanup.destroy)
connections:extend(cleanup.call, {function()
	repeat
		local success = pcall(task.cancel, running)
		task.wait()
	until success or coroutine.status(running) == "dead"
end})

local finished_init = false
UniPlugin:onClose(function()
	if Manager.Storage then
		if Manager.Storage.repoRoots then
			Manager.Storage.repoRoots:ClearAllChildren()
		end
	end

	if finished_init and Manager.getSetting("SaveOnClose") then
		Manager.saveState()
	end
	connections:free()
end)

-- Public jexplorer plugin api
UniPlugin:setApi("manager", Manager)
UniPlugin:setApi("editors", {
	ContextMenuEditor = require(editors.ContextMenuEditor),
	ExplorerFrameEditor = require(editors.ExplorerFrameEditor),
	InstanceVisibilityEditor = require(editors.InstanceVisibilityEditor),
	JextensionEditor = require(editors.JextensionEditor),
	OrderEditor = require(editors.OrderEditor),
	PropertyEditor = require(editors.PropertyEditor),
	SettingEditor = require(editors.SettingEditor),
	StateEditor = require(editors.StateEditor),
	StyleEditor = require(editors.StyleEditor),
	VersionControl = require(editors.VersionControl)
})
UniPlugin:setApi("dependencies", {
	Selector = require(shared.Small.Selector),
	Style = require(shared.Style),
	PluginUI = require(shared.PluginUI),
	ClassIcon = require(shared.Small.ClassIcon),
	UDF = require(shared.UDF),
	SavedState = SavedState,
	GitAPI = require(shared.GitAPI),
	PluginManager = require(shared.PluginManager),
	JQL = require(shared.JQL),
	create = require(shared.create)
})

local function pluginui<T>(name: string, create: () -> T): T
	local success
	local item: any
	while true do
		success, item = pcall(create)
		if success then break end
		task.wait()
	end
	item.Name = "Jexplorer_TOOLBAR"
	return item
end

local getId = PluginUI.getId
local pluginToolbar = pluginui("TOOLBAR", function()
	return plugin:CreateToolbar("athar_adv's tools")
end)

local uiToggleButton = pluginui("UITOGGLE", function()
	return pluginToolbar:CreateButton(
		getId("ToggleUI"), "Toggle the explorer widget visibility",
		"rbxassetid://112696382020883", "Jexplorer"
	)
end)

local jexplorerWidget = pluginui("", function()
	return plugin:CreateDockWidgetPluginGuiAsync(
		getId("Jexplorer"), DockWidgetPluginGuiInfo.new(
			Enum.InitialDockState.Left,
			false,
			false
		)
	)
end)

jexplorerWidget:AddTag("JEXP_Widget")
jexplorerWidget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
jexplorerWidget.Title = `Jexplorer @{VERSION}`
jexplorerWidget.Name = "Jexplorer"

jexplorerWidget:BindToClose(function()
	jexplorerWidget.Enabled = not jexplorerWidget.Enabled
end)
jexplorerWidget:GetPropertyChangedSignal("Enabled"):Connect(function()
	uiToggleButton:SetActive(jexplorerWidget.Enabled)
end)

local function init()
	finished_init = true
	Manager.Toolbar = pluginToolbar
	Manager.init(connections, jexplorerWidget)

	jexplorerWidget.Enabled = true
	uiToggleButton:SetActive(true)
end

uiToggleButton.ClickableWhenViewportHidden = true
uiToggleButton.Click:Connect(function()
	if not finished_init then
		uiToggleButton:SetActive(false)
		init()
	else
		local state = Manager.WidgetState
		local widget = state.Widget

		widget.Enabled = not widget.Enabled
	end
end)

if jexplorerWidget.HostWidgetWasRestored and not finished_init then
	init()
end

UniPlugin:exportAs("athar-adv/Jexplorer")