--!strict
--!optimize 2

local ScriptEditorService = game:GetService("ScriptEditorService")

local Types = require("../Types")
local Jecs = require("../Jecs")
local RawInst = require("./Small/RawInst")
local JexpTags = require("./Small/JexpTags")
local Constants = require("../Constants")
local JQL = require("./JQL")

local getClassPropMap = require("./Small/GetClassPropMap")

local ItemGroup = require("./Vendor/ItemGroup")

local OrderEditor = require("../Editors/OrderEditor")
local VersionControl = require("../Editors/VersionControl")

local profilebegin, profileend = debug.profilebegin, debug.profileend
local rbxconnect = RawInst.rbxconnect
local getpropertychangedsignal = RawInst.getpropertychangedsignal
local getattributes = game.GetAttributes
local rawindex = RawInst.rawindex
local index = RawInst.index
local bulkinsert = Jecs.bulk_insert
local pair = Jecs.pair
local childof = Jecs.ChildOf
local getchildren = game.GetChildren

local match = string.match

local move = table.move
local sort = table.sort
local concat = table.concat
local clear = table.clear
local insert = table.insert
local clone = table.clone

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

local hierarchyCache = {
	childrenMap = {} :: {[ExplorerEntity]: {ExplorerEntity}},
	roots = {} :: {ExplorerEntity},
}
local currentSelection = {}
local classPriorityCache = {}
--local lastCacheVersion = -1
local lastPriorityVersion = -1
local SUFFIX_PATTERN = "(%d+)$"

local flatCache = {
	flattenedEntities = nil :: any,
	entityToIndex = nil :: any,
	indexToIndentOffset = nil :: any,
	version = 0
}

local entityStateCache = {}

local function getClassPriority(className: string): number
	profilebegin("Jexplorer::getClassPriorityUncached")
	local priority = table.find(OrderEditor.ClassPriorities, className) or OrderEditor.DefaultPriority
	classPriorityCache[className] = priority
	profileend()
	return priority
end

local function addEntityPriorities(Manager: Manager, children: {ExplorerEntity}, priorities: any)
	profilebegin("Jexplorer::getEntityPriorities")
	local components = Manager.Components
	local state = Manager.WidgetState
	local world = state.World

	if OrderEditor.PriorityVersion and OrderEditor.PriorityVersion ~= lastPriorityVersion then
		table.clear(classPriorityCache)
		lastPriorityVersion = OrderEditor.PriorityVersion
	end

	profilebegin("Jexplorer::getEntityPriorities::collectEntityPriority")
	profilebegin("Jexplorer::getEntityPriorities::collectEntityPriority::loop1")
	local n = 0
	for i, child in children do
		n += 1
		local classname = Manager.ClassNameCache[child]
		if classname then
			priorities[child] = classPriorityCache[classname] or getClassPriority(classname)
		end
	end
	profileend()
	profilebegin("Jexplorer::getEntityPriorities::collectEntityPriority::loop2")
	for i = #children, 1, -1 do
		if not priorities[children[i]] then
			children[i] = children[n]
			children[n] = nil
			n -= 1
		end
	end
	profileend()

	profileend()
	profileend()

	return priorities
end

local HierarchyState = {
	StateCache = entityStateCache,
	ChildrenMap = hierarchyCache.childrenMap
} :: Types.HierarchyState

function HierarchyState.cleanupEntity(Manager, ent)
	local state = Manager.WidgetState
	local world, components = state.World, Manager.Components
	world:add(ent, components.Deleted)

	HierarchyState.queueForEntityCleanup(Manager, ent)

	local function addchildren(entity: ExplorerEntity)
		for _, v in HierarchyState.getChildren(entity) do
			HierarchyState.cleanupEntity(Manager, v)
			addchildren(v)
		end
	end
	addchildren(ent)

	Manager.DeferredTaskScheduler.EntityCleanupQueuePending = true
end

function HierarchyState.rebuildHierarchyCache(Manager)
	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World

	local roots, childrenMap = {}, {}

	profilebegin("Jexplorer::rebuildHierarchyCache")
	profilebegin("Jexplorer::rebuildHierarchyCache::buildChildrenMap")

	for _, eid in state.Entities do
		if Manager.isExplorerEntityDeleted(eid) then
			continue
		end

		HierarchyState.invalidateEntityStateCache(eid)
		local parent = Manager.parentOf(eid)
		if not parent then
			table.insert(roots, eid)
		else
			local tbl = childrenMap[parent]
			if not tbl then
				tbl = {}
				childrenMap[parent] = tbl
			end
			table.insert(tbl, eid)
		end
	end

	profileend()
	profilebegin("Jexplorer::rebuildHierarchyCache::collectPriorities")

	local tosort = {roots}

	local mergedPriorities = {}
	addEntityPriorities(Manager, roots, mergedPriorities)
	for parent, children in childrenMap do
		addEntityPriorities(Manager, children, mergedPriorities)
	end

	for parent, children in childrenMap do
		table.insert(tosort, children)
	end

	profileend()
	profilebegin("sort")

	local nameCache = Manager.NameCache
	local function sort(a, b)
		local priorityA = mergedPriorities[a]
		local priorityB = mergedPriorities[b]
		if priorityA ~= priorityB then
			return priorityA < priorityB
		end
		local nameA = nameCache[a]
		local nameB = nameCache[b]

		local numA = match(nameA, SUFFIX_PATTERN)
		local numB = match(nameB, SUFFIX_PATTERN)

		if numA and numB then
			return tonumber(numA) < tonumber(numB)
		end
		return nameA < nameB
	end

	-- Sort all at once for best performance
	for _, v in tosort do
		table.sort(v, sort)
	end

	profileend()

	hierarchyCache.childrenMap = childrenMap
	hierarchyCache.roots = roots
	HierarchyState.ChildrenMap = childrenMap

	profileend()
	return childrenMap, roots
end

function HierarchyState.getFlattenedVisibleEntities(Manager, invisible, isScroller)
	profilebegin("Jexplorer::getFlattenedVisibleEntities")

	-- Return cached result if valid
	if flatCache.flattenedEntities then
		profileend()
		return flatCache.flattenedEntities, flatCache.entityToIndex, flatCache.indexToIndentOffset
	end

	local state = Manager.WidgetState
	local components = Manager.Components
	local childrenMap = hierarchyCache.childrenMap
	local roots = hierarchyCache.roots
	local world = state.World

	local flatList = table.create(1000) -- Pre-allocate larger
	local indexToIndentOffset = table.create(1000)
	local entityToIndex = {}
	local flatIndex = 0

	local stack = table.create(500)
	local stackSize = 0
	local indentStack = table.create(500)

	-- flatCache function references (avoid lookups)
	local entityinvisible = Manager.isExplorerEntityInvisible
	local entitydeleted = Manager.isExplorerEntityDeleted
	local entityexpanded = Manager.isExplorerEntityExpanded

	profilebegin("traverse")

	-- Push roots onto stack
	local rootCount = #roots
	for i = rootCount, 1, -1 do
		stackSize += 1
		stack[stackSize] = roots[i]
		indentStack[stackSize] = 0
	end

	while stackSize > 0 do
		local entity = stack[stackSize]
		local currentIndent = indentStack[stackSize]
		stackSize -= 1

		-- Get cached state
		local state = entityStateCache[entity]
		if not state then
			profilebegin("generateentitystate")
			local invisible = entityinvisible(entity) or entitydeleted(entity)
			local expanded = entityexpanded(entity)
			state = {invisible = invisible, expanded = expanded, isTopLevel = world:has(entity, components.DisplayTopLevel)}
			entityStateCache[entity] = state
			profileend()
		end

		if not state.invisible then -- Skip invisible entirely
			flatIndex += 1
			flatList[flatIndex] = entity
			indexToIndentOffset[flatIndex] = currentIndent
			entityToIndex[entity] = flatIndex -- Build inline

			if state.expanded then
				local children = childrenMap[entity]
				if children then
					local childCount = #children
					if childCount > 0 then
						local childIndent = currentIndent + 1
						-- Push in reverse order for correct traversal
						for i = childCount, 1, -1 do
							stackSize += 1
							stack[stackSize] = children[i]
							indentStack[stackSize] = childIndent
						end
					end
				end
			end
		end
	end

	profileend()

	flatCache.flattenedEntities = flatList
	flatCache.entityToIndex = entityToIndex
	flatCache.indexToIndentOffset = indexToIndentOffset

	profileend()
	return flatList, entityToIndex, indexToIndentOffset
end

function HierarchyState.flushEntityCreationQueue(Manager)
	local state = Manager.WidgetState
	local world = state.World
	local components = Manager.Components

	local query = Jecs.cached_query(world, components.CreationTicket)

	local any = false
	local anyEntities = false
	for parent, ticketData in query:iter() do
		any = true
		world:remove(parent, components.CreationTicket)

		for _, child in ticketData.children do
			if Manager.isInstanceExcluded(child) then continue end
			if Manager.InstanceToEntity[child] then continue end
			anyEntities = true

			HierarchyState.createInstanceEntity(Manager, child, parent)
		end
	end
	if not any then return false end

	if anyEntities then
		Manager.DeferredTaskScheduler.EntityCreationQueuePending = true
	end

	return true
end

function HierarchyState.queueForEntityCreation(Manager, parent, children)
	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World

	world:set(parent, components.CreationTicket, {children = children})
end

function HierarchyState.flushEntityCleanupQueue(Manager)
	profilebegin("Jexplorer::cleanupEntitiesInTable")

	local state = Manager.WidgetState
	local world = state.World
	local queryWorld = state.QueryWorld
	local components = Manager.Components

	local groupsToFree = {}
	local entityIndexesToRemove = {}
	local seen = {}
	local any = false

	local entities = state.Entities
	local entitiesWithTicket = {}

	local query = Jecs.cached_query(world, components.CleanupTicket)
	for entity, ticketData in query:iter() do
		world:remove(entity, components.CleanupTicket)
		-- Prevent double cleanups
		if seen[entity] then continue end
		seen[entity] = true
		world:add(entity, components.Deleted)

		any = true

		table.insert(entitiesWithTicket, entity)

		profilebegin("Jexplorer::processImmediateCleaningTasks")
		local inst = world:get(entity, components.Instance)
		if inst then
			Manager.InstanceToEntity[inst] = nil
		end

		local entityConnections = world:get(entity, components.Connections)
		if entityConnections then
			table.insert(groupsToFree, entityConnections)
		end

		local frame = world:get(entity, components.Frame)
		if frame then
			Manager.ExplorerFrame.queueForExplorerFrameReuse(Manager, frame, entity)
		end

		world:add(entity, components.Deleted)

		Manager.VirtualScroller.invalidateEntityHeightCache(Manager, entity)
		Manager.updateParent(entity, nil)

		local idx = table.find(state.Entities, entity)
		if idx then table.remove(state.Entities, idx) end

		profileend()
	end
	if not any then profileend() return false end

	profilebegin("Jexplorer::cleanupEntity::processDeferredCleaningTasks")
		profilebegin("freeGroups")
		for _, group in groupsToFree do
			(group::ItemGroup.ItemGroup):free()
		end
		profileend()

		profilebegin("removeEntityData")
		for _, entity in entitiesWithTicket do
			Manager.ClassNameCache[entity] = nil
			Manager.NameCache[entity] = nil
			Manager.ChildAmountCache[entity] = nil
		end
		profileend()

		profilebegin("deleteEntities")
		for _, entity in entitiesWithTicket do
			--world:delete(entity)
			HierarchyState.queueForEntityDeletion(Manager, entity)
		end
		profileend()
	profileend()

	profileend()
	return true
end

function HierarchyState.queueForEntityCleanup(Manager, entity)
	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World

	world:set(entity, components.CleanupTicket, {})
end

function HierarchyState.flushOnChildAddedQueue(Manager)
	profilebegin("Jexplorer::flushOnChildAddedQueue")
	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World

	local anyChanged = false
	local seen = {}

	local query = Jecs.cached_query(world, components.ChildAddedTicket)

	for newParent, ticketData in query:iter() do
		world:remove(newParent, components.ChildAddedTicket)

		for _, childInst in ticketData.children do
			if seen[childInst] then continue end
			seen[childInst] = true  -- ADD THIS LINE!

			if not childInst:IsDescendantOf(game) then continue end
			local childEntity = HierarchyState.getOrCreateInstanceEntity(Manager, childInst, newParent)

			local frame = world:get(newParent, components.Frame)
			if not frame then continue end
			local frameState = state.FrameWorld:get(frame, components.FrameState)
			if frameState then
				-- If a child is added, the entity always has atleast 1 child so just set to true always
				--main.Arrow.Visible = #instance:GetChildren() > 0
				frameState.arrow.Visible = true
			end
			HierarchyState.updateEntityOrder(Manager, childEntity)
		end

		if Manager.isExplorerEntityExpanded(newParent) then
			Manager.VirtualScroller.invalidateEntityHeightCache(Manager, newParent)
		end
		anyChanged = true
	end
	if not anyChanged then return false end

	HierarchyState.flushEntityPropAssignQueue(Manager)

	profileend()
	return anyChanged
end

function HierarchyState.queueForOnChildAdded(Manager, newParent, childEntity)
	local state = Manager.WidgetState
	local world = state.World
	local components = Manager.Components

	local ticket: {children: {Instance}}
	local existing = world:get(newParent, components.ChildAddedTicket)
	if existing then
		ticket = existing
	else
		ticket = {children = {}}
		world:set(newParent, components.ChildAddedTicket, ticket)
	end
	table.insert(ticket.children, childEntity)
end

function HierarchyState.flushOnChildRemovedQueue(Manager)
	profilebegin("Jexplorer::flushOnChildRemovedQueue")

	local components = Manager.Components
	local state = Manager.WidgetState
	local world = state.World
	local anyChanged = false
	local seen = {}

	local query = Jecs.cached_query(world, components.ChildMovedTicket)

	for oldParent, ticketData in query:iter() do
		world:remove(oldParent, components.ChildMovedTicket)

		for _, childInst in ticketData.children do
			if seen[childInst] then continue end
			seen[childInst] = true

			-- Update OLD parent's arrow visibility
			local frame = world:get(oldParent, components.Frame)
			if frame then
				local frameInst = Manager.frameOf(frame)
				if frameInst then
					local frameState = state.FrameWorld:get(frame, components.FrameState)
					if frameState then
						local parentInstance = world:get(oldParent, components.Instance)
						--- Check if OLD parent still has remaining children
						frameState.arrow.Visible = if parentInstance then #parentInstance:GetChildren() > 0 else false
					end
				end
			end

			-- Handle the child entity if it was reparented (not deleted)
			if childInst.Parent then
				local newParent = Manager.InstanceToEntity[childInst.Parent]
				local childEntity = Manager.InstanceToEntity[childInst]
				if newParent then
					if childEntity then
						if Manager.isExplorerEntityExpanded(newParent) then
							Manager.VirtualScroller.invalidateEntityHeightCache(Manager, childEntity)
						end
						Manager.updateParent(childEntity, newParent)

						HierarchyState.updateEntityOrder(Manager, childEntity)
						anyChanged = true
					end
				else
					HierarchyState.cleanupEntity(Manager, childEntity)
				end
			end
		end

		-- Invalidate old parent's flatCache
		if Manager.isExplorerEntityExpanded(oldParent) then
			Manager.VirtualScroller.invalidateEntityHeightCache(Manager, oldParent)
		end
	end
	if not anyChanged then return false end

	HierarchyState.flushEntityPropAssignQueue(Manager)

	profileend()
	return anyChanged
end

function HierarchyState.queueForOnChildRemoved(Manager, oldParent, childEntity)
	local state = Manager.WidgetState
	local world = state.World
	local components = Manager.Components

	local ticket: {children: {Instance}}
	local existing = world:get(oldParent, components.ChildMovedTicket)
	if existing then
		ticket = existing
	else
		ticket = {children = {}}
		world:set(oldParent, components.ChildMovedTicket, ticket)
	end
	table.insert(ticket.children, childEntity)
end

local entityDeletionQuery
function HierarchyState.stepFlushEntityDeletionQueue(Manager)
	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World
	local queryWorld = state.QueryWorld

	local n = 0
	local any = false

	local query
	if entityDeletionQuery then
		query = entityDeletionQuery
	else
		entityDeletionQuery = world:query(components.DeletionTicket)
			:cached()
		query = entityDeletionQuery
	end
	if not query:iter()() then return false end

	local stepSize = Manager.getSetting("EntityDeletionFlushStep")
	for entity, ticketData in query:iter() do
		if n >= stepSize then break end
		any = true
		n += 1
		world:remove(entity, components.DeletionTicket)

		local idx = table.find(state.Entities, entity)
		if idx then table.remove(state.Entities, idx) end

		HierarchyState.deregisterFromQueryWorld(Manager, entity)
	end

	return any
end

function HierarchyState.queueForEntityDeletion(Manager, entity)
	local state = Manager.WidgetState
	local world = state.World
	local components = Manager.Components

	world:set(entity, components.DeletionTicket, {})
end

local function onChange(world: World, components: Types.Components, Manager: Manager, ent: ExplorerEntity, instance: Instance, v: string, oldValueCache: {[string]: any})
	local frame = world:get(ent, components.Frame)
	if not frame then return end

	local frameInst = Manager.frameOf(frame)
	if not frameInst then return end

	local oldValue = oldValueCache[v]
	local value = rawindex(instance, v)
	local oldTag = JexpTags.getTaggedPropertyTag(v, typeof(oldValue) == "EnumItem" and oldValue.Name or tostring(oldValue))
	local newTag = JexpTags.getTaggedPropertyTag(v, typeof(value) == "EnumItem" and value.Name or tostring(value))

	frameInst:RemoveTag(oldTag)
	frameInst:AddTag(newTag)
	oldValueCache[v] = value
end

local function onChangeRunContext(world: World, components: Types.Components, Manager: Manager, ent: ExplorerEntity, instance: Instance, oldValueCache: {[string]: any})
	local frame = world:get(ent, components.Frame)
	if not frame then return end

	local frameInst = Manager.frameOf(frame)
	if not frameInst then return end

	local value = rawindex(instance, "RunContext")
	frameInst.Name = Constants.ExplorerFrameScriptClassNameFormat:format(value.Name)
	oldValueCache.RunContext = value
end

local function onChanged(
	world: World, frameWorld: World, components: Types.Components, Manager: Manager, ent: ExplorerEntity, instance: Instance,
	handlers: {[string]: () -> ()}, nameCache: {[ExplorerEntity]: string}, classNameCache: {[ExplorerEntity]: string}, prop: string
)
	if prop == "Name" then
		nameCache[ent] = instance.Name
		local frame = world:get(ent, components.Frame)
		if frame then
			local frameState = frameWorld:get(frame, components.FrameState)
			if frameState and frameState.instanceName.Text ~= instance.Name then
				local instanceName = frameState.instanceName
				instanceName.Text = instance.Name
				local bounds: Vector2 = rawindex(instanceName, "TextBounds")
				local x = instanceName:GetStyled("Position").X.Offset + bounds.X
				frameState.insertInstanceButton.Position = UDim2.new(0, x + 10, 0.5)
				frameState.pendingChanges.Position = UDim2.new(0, x + 26, 0.5)
			end
		end
	elseif prop == "ClassName" then
		classNameCache[ent] = instance.ClassName
	end
	local handler = handlers[prop]
	if not handler then return end
	handler()
end

local function onChildAdded(Manager: Manager, ent: ExplorerEntity, child: Instance)
	if Manager.isInstanceExcluded(child) then return end

	local existing = Manager.InstanceToEntity[child]
	if existing and Manager.WidgetState.World:has(existing, Manager.Components.AncestryLocked) then
		return
	end

	HierarchyState.queueForOnChildAdded(Manager, ent, child)
	Manager.DeferredTaskScheduler.EntityChildAddedQueuePending = true
end

local function onChildRemoved(Manager: Manager, ent: ExplorerEntity, child: Instance)
	local world = Manager.WidgetState.World
	local components = Manager.Components
	local existing = Manager.InstanceToEntity[child]
	if existing and world:has(existing, components.AncestryLocked) then
		return
	end

	if child.Parent == nil then return end
	if existing then
		local parent = Manager.parentOf(existing)
		if parent then
			local inst = Manager.instanceOf(parent)
			if inst and Manager.isInstanceExcluded(inst) then
				return
			end
		end
	end

	HierarchyState.queueForOnChildRemoved(Manager, ent, child)
	Manager.DeferredTaskScheduler.EntityChildRemovedQueuePending = true
end

local function onParentChanged(Manager: Manager, ent: ExplorerEntity, instance: Instance)
	if rawindex(instance, "Parent") == nil then
		HierarchyState.cleanupEntity(Manager, ent)
		Manager.DeferredTaskScheduler.EntityCleanupQueuePending = true
	end
end

local function onDestroying(Manager: Manager, ent: ExplorerEntity)
	HierarchyState.cleanupEntity(Manager, ent)
	Manager.DeferredTaskScheduler.EntityCleanupQueuePending = true
end

local function onAttributeChanged(Manager: Manager, ent: ExplorerEntity, instance: Instance, currentAttributes: {[string]: any}, attrName: string)
	local queryWorld = Manager.WidgetState.QueryWorld
	local components = Manager.Components
	local usages = Manager.AttributeUsageAmounts
	local attrValue = instance:GetAttribute(attrName)

	if attrValue ~= nil and not currentAttributes[attrName] then
		currentAttributes[attrName] = true
		if usages[attrName] then
			usages[attrName] += 1
		else
			usages[attrName] = 1
		end
		local attrComp = Manager.QueryAttrMap[attrName]
		if not attrComp then
			attrComp = queryWorld:entity()
			queryWorld:set(attrComp::any, Jecs.Name, attrName)
			Manager.QueryAttrMap[attrName] = attrComp
		end

		Manager.refreshQueryAttributes(ent, attrComp)
	elseif attrValue == nil and currentAttributes[attrName] then
		currentAttributes[attrName] = nil
		if usages[attrName] then
			usages[attrName] -= 1
		else
			usages[attrName] = 0
		end
		Manager.refreshQueryAttributes(ent, nil)

		if usages[attrName] == 0 then
			local attrComp = Manager.QueryAttrMap[attrName]
			if attrComp then
				for queryEnt, linked in queryWorld:query(components.QueryLinkedEntities, attrComp):iter() do
					for _, ent in linked do
						Manager.refreshQueryTags(ent, nil)
					end
					if #linked == 0 then
						queryWorld:delete(queryEnt)
					end
				end
				queryWorld:delete(attrComp)
			end
			Manager.QueryAttrMap[attrName] = nil
			usages[attrName] = nil
		end
	end
end

local appliedTaggedProps = {}
function HierarchyState.flushEntityPropAssignQueue(Manager)
	local world = Manager.WidgetState.World
	local queryWorld = Manager.WidgetState.QueryWorld
	local components = Manager.Components

	local nameCache = Manager.NameCache
	local classNameCache = Manager.ClassNameCache
	local childAmountCache = Manager.ChildAmountCache
	local any = false

	local query = Jecs.cached_query(world, components.PropAssignTicket)

	profilebegin("Jexplorer::flushEntityPropAssignQueue")
	for ent, ticketData in query:iter() do
		any = true
		world:remove(ent, components.PropAssignTicket)

		local instance = world:get(ent, components.Instance)
		if not instance then continue end

		local connections = assert(world:get(ent, components.Connections))
		local id = Manager.getInstId(instance)
		if id then
			local color = Manager.IconColorMap[id]
			if color then
				world:set(ent, components.FrameIconColor, color)
			end
		end

		local className = instance.ClassName
		nameCache[ent] = instance.Name
		classNameCache[ent] = className
		childAmountCache[ent] = #instance:GetChildren()

		if ticketData.ty ~= "instance" then continue end

		if table.find(currentSelection, instance) then
			world:add(ent, components.Selected)
		end

		local currentAttributes = {}
		local oldValueCache = {}
		local handlers = {}

		local props = getClassPropMap(className)
		assert(props)

		local n = 0
		for _, name in Constants.TaggedProperties do
			if not props[name] then continue end
			n += 1
			appliedTaggedProps[n] = name
		end

		for attrName in getattributes(instance) do
			currentAttributes[attrName] = true
			local usages = Manager.AttributeUsageAmounts
			if usages[attrName] then
				usages[attrName] += 1
			else
				usages[attrName] = 1
			end
			local attrComp = Manager.QueryAttrMap[attrName]
			if not attrComp then
				attrComp = queryWorld:entity()
				queryWorld:set(attrComp::any, Jecs.Name, attrName)
				Manager.QueryAttrMap[attrName] = attrComp
			end
		end
		for _, v in appliedTaggedProps do
			oldValueCache[v] = rawindex(instance, v)
			handlers[v] = function() onChange(world, components, Manager, ent, instance, v, oldValueCache) end
		end
		clear(appliedTaggedProps)

		if className == "Script" then
			-- RunContext needs to be special cased because its ExplorerFrames are named differently
			handlers.RunContext = function() onChangeRunContext(world, components, Manager, ent, instance, oldValueCache) end
		end

		local packageLink = instance:FindFirstChildOfClass("PackageLink")
		if packageLink then
			world:add(ent, components.HasPackageLink)
		end

		local n = #connections.items
		connections.items[n+1] = rbxconnect(rawindex(instance, "Changed"), function(prop: string)
			onChanged(world, Manager.WidgetState.FrameWorld, components, Manager, ent, instance, handlers, nameCache, classNameCache, prop)
		end)
		connections.items[n+2] = rbxconnect(rawindex(instance, "ChildAdded"), function(child: Instance)
			if rawindex(child, "ClassName") == "PackageLink" then
				packageLink = child :: any
				world:add(ent, components.HasPackageLink)
			end
			onChildAdded(Manager, ent, child)
		end)
		connections.items[n+3] = rbxconnect(rawindex(instance, "ChildRemoved"), function(child: Instance)
			if child == packageLink then
				world:remove(ent, components.HasPackageLink)
			end
			onChildRemoved(Manager, ent, child)
		end)
		connections.items[n+4] = rbxconnect(getpropertychangedsignal(instance, "Parent"), function()
			onParentChanged(Manager, ent, instance)
		end)
		connections.items[n+5] = rbxconnect(rawindex(instance, "AttributeChanged"), function(attrName: string)
			onAttributeChanged(Manager, ent, instance, currentAttributes, attrName)
		end)
		connections.items[n+6] = rbxconnect(rawindex(instance, "Destroying"), function()
			onDestroying(Manager, ent)
		end)

		Manager.refreshQueryAttributes(ent, nil)
	end

	profileend()
	return any
end

local archetypeToQueryEntity: {[string]: Jecs.Id} = {}

function HierarchyState.deregisterFromQueryWorld(Manager, entity)
	local components = Manager.Components
	local state = Manager.WidgetState
	local world = state.World
	local queryWorld = state.QueryWorld

	local queryEntity = world:get(entity, components.LinkedQueryEntity)
	if queryEntity then
		local linked = queryWorld:get(queryEntity, components.QueryLinkedEntities)
		if linked then
			if #linked == 1 then
				queryWorld:delete(queryEntity)
				local hash = queryWorld:get(queryEntity, components.QUERYWORLD_ComponentHash)
				if hash then
					archetypeToQueryEntity[hash] = nil
				end
			else
				local idx = table.find(linked, entity)
				if idx then
					table.remove(linked, idx)
				end
			end
		end
	end
	world:remove(entity, components.LinkedQueryEntity)
end

local temporaryComponentArray: {Jecs.Id} = {}
function HierarchyState.registerIntoQueryWorld(Manager, entity, className, tags, attributes)
	if not className then
		warn("classname aint it")
		return
	end

	local state = Manager.WidgetState
	local world = state.World
	local queryWorld = state.QueryWorld
	local components = Manager.Components

	local classComponents = Manager.getPropertyComponentsForClass(queryWorld, className)

	local n = #classComponents
	local tn = #tags
	move(classComponents, 1, n, 1, temporaryComponentArray)
	move(tags, 1, tn, n + 1, temporaryComponentArray)
	move(attributes, 1, #attributes, n + tn + 1, temporaryComponentArray)

	sort(temporaryComponentArray)
	local hash = concat(temporaryComponentArray, ",")

	local existingQueryEntity = archetypeToQueryEntity[hash]
	if existingQueryEntity then
		clear(temporaryComponentArray)
		local linked = queryWorld:get(existingQueryEntity, components.QueryLinkedEntities)
		if not linked then
			world:set(entity, components.PropAssignTicket, {ty = "instance"})
			return
		end

		world:set(entity, components.LinkedQueryEntity, existingQueryEntity)
		table.insert(linked, entity)
		return existingQueryEntity
	end

	local linked = {entity}

	-- Query entity not found, create new
	local queryEntity = Jecs.entity_with_archetype(queryWorld, true, temporaryComponentArray)
	queryWorld:set(queryEntity, components.QUERYWORLD_ComponentHash, hash)
	queryWorld:set(queryEntity, components.QueryLinkedEntities, linked)

	world:set(entity, components.LinkedQueryEntity, queryEntity)

	archetypeToQueryEntity[hash] = queryEntity

	clear(temporaryComponentArray)
	return queryEntity
end

-- TODO: Should probably make another version of this since i dont know how the hell this even works, but for now keep it
function HierarchyState.getEntitiesBetween(Manager, e1, e2)
	profilebegin("Jexplorer::getEntitiesBetween")
	local visible = HierarchyState.getFlattenedVisibleEntities(Manager, {}, false)
	local i1, i2
	for i, eid: any in visible do
		if eid == e1 then i1 = i end
		if eid == e2 then i2 = i end
		if i1 and i2 then break end
	end
	if not i1 or not i2 then return {} end
	if i1 > i2 then i1, i2 = i2, i1 end
	local slice = {}
	for i = i1, i2 do
		table.insert(slice, visible[i])
	end
	profileend()
	return slice
end

function HierarchyState.updateEntityOrder(Manager, entity)
	profilebegin("Jexplorer::updateEntityOrder")
	local state = Manager.WidgetState
	local world = state.World
	local components = Manager.Components

	local i = table.find(state.Entities, entity)
	if i then
		table.remove(state.Entities, i)
	end

	table.insert(state.Entities, entity)
	profileend()
end

function HierarchyState.getChildren(entity)
	return hierarchyCache.childrenMap[entity] or {}
end

local VIRTUAL_ENTITY_PREFAB: {Jecs.Id}
function HierarchyState.createVirtualEntity(Manager, options, extra)
	assert(options.ClassName, "ClassName required")
	assert(options.Name, 'Name required')
	assert(options.ChildAmount, "ChildAmount required")

	local state = Manager.WidgetState
	local components = Manager.Components
	local queryWorld = state.QueryWorld
	local world = state.World
	local cleanup = Manager.CleanupFns

	local connections: Types.ItemGroup<RBXScriptConnection> = options.Parent and world:get(options.Parent, components.Connections)::any or state.Connections

	local tbl = {
		ClassName = options.ClassName,
		Name = options.Name,
	}

	for k, v in extra do
		tbl[k] = v
	end
	local ent = world:entity()

	table.insert(state.Entities, ent)
	if not VIRTUAL_ENTITY_PREFAB then
		VIRTUAL_ENTITY_PREFAB = {
			components.Instance,
			components.Connections,
			components.PropAssignTicket
		}
	end
	local instConnections = connections:extend(cleanup.call)
	bulkinsert(world, ent, VIRTUAL_ENTITY_PREFAB, {
		tbl,
		instConnections,
		{ty = "virtual"}
	})

	return ent
end

function HierarchyState.createInstanceEntity(Manager, instance, parent, dontSetInInstanceToEntity)
	profilebegin("Jexplorer::createInstanceEntity")
	local state = Manager.WidgetState
	local components = Manager.Components
	local cleanup = Manager.CleanupFns

	local world = state.World
	local queryWorld = state.QueryWorld
	local jexplorerUi = state.JExplorerUI

	local components = Manager.Components
	local state = Manager.WidgetState

	local parentConnections = state.Connections
	if parent then
		local connections = world:get(parent, components.Connections)
		if not connections then
			connections = ItemGroup.create(cleanup.disconnect)
			world:set(parent, components.Connections, connections::any)
		end
		parentConnections = connections::any
	end

	local connections = parentConnections:extend(cleanup.disconnect)
	local className = instance.ClassName

	local entity = Jecs.entity_with_archetype(world,
		false,
		{
			components.Instance,
			components.Connections,
			components.ParentTabs,
			components.PropAssignTicket,
			if parent then pair(childof, parent) :: any else nil,
		},
		instance,
		connections,
		{},
		{ty = "instance"}
	)

	local children = getchildren(instance)
	if #children > 0 then
		world:set(entity, components.CreationTicket, {children = children})
	end
	if parent then
		local value = Manager.ChildAmountCache[parent]

		Manager.ChildAmountCache[parent] = value and value + 1 or 1
	end

	insert(state.Entities, entity)
	if not dontSetInInstanceToEntity then
		Manager.InstanceToEntity[instance] = entity
	end
	profileend()

	return entity
end

function HierarchyState.getOrCreateInstanceEntity(Manager, instance, ...)
	local instanceToEntity = Manager.InstanceToEntity
	local cached = instanceToEntity[instance]

	if cached then
		return cached
	end
	return HierarchyState.createInstanceEntity(Manager, instance, ...)
end

function HierarchyState.invalidateEntityStateCache(entity: ExplorerEntity)
	entityStateCache[entity] = nil
	flatCache.version += 1
	flatCache.flattenedEntities = nil
	JQL.clearParentTreeCache()  -- hierarchy changed, ancestor chains are stale
end

game:GetService("Selection").SelectionChanged:Connect(function()
	currentSelection = game:GetService("Selection"):Get()
end)

return HierarchyState


local INSERT_SUGGESTED_CLASSES: {[string]: {string}} = {
	Workspace = {
		"Part",
		"MeshPart",
		"Model",
		"Folder"
	},
	Players = {},
	CoreGui = {
		"ScreenGui",
	},
	Lighting = {
		"Sky",
		"Atmosphere",
		"SunRaysEffect",
		"ColorCorrectionEffect",
		"BlurEffect",
		"BloomEffect",
		"DepthOfFieldEffect"
	},
	MaterialService = {
		"MaterialVariant",
		"Folder"
	},
	NetworkClient = {},
	PluginGuiService = {},
	ReplicatedFirst = {
		"LocalScript",
		"Folder"
	},
	ReplicatedStorage = {
		"Folder",
		"ModuleScript",
		"RemoteEvent",
		"RemoteFunction"
	},
	ServerScriptService = {
		"Script",
		"ModuleScript",
		"Folder"
	},
	ServerStorage = {
		"Script",
		"ModuleScript",
		"Folder"
	},
	SoundService = {
		"Sound",
		"SoundGroup",
		"Folder"
	},
	StarterGui = {
		"Frame",
		"TextButton",
		"TextLabel",
		"TextBox",
		"ImageButton",
		"ImageLabel",
		"ScreenGui",
		"BillboardGui",
		"SurfaceGui"
	},
	StarterPack = {
		"Tool",
		"LocalScript"
	},
	StarterPlayer = {
		"LocalScript",
		"Folder",
		"BindableFunction",
		"BindableEvent"
	},
	Teams = {
		"Team"
	},
	TextChatService = {
		"TextChatCommand",
		"TextChannel"
	},
	StylingService = {
		"StyleSheet",
		"StyleRule",
		"StyleDerive"
	}
}
return INSERT_SUGGESTED_CLASSES


return {
	"Frame",
	"ForceField",
	"Folder",
	"FloorWire",
	"FloatCurve",
	"FlangeSoundEffect",
	"Fire",
	"FileMesh",
	"Explosion",
	"EulerRotationCurve",
	"EqualizerSoundEffect",
	"EchoSoundEffect",
	"DynamicRotate",
	"Dragger",
	"DoubleConstrainedValue",
	"DockWidgetPluginGui",
	"DistortionSoundEffect",
	"DialogChoice",
	"Dialog",
	"DepthOfFieldEffect",
	"Decal",
	"CustomSoundEffect",
	"CoreGui",
	"CylindricalConstraint",
	"CylinderMesh",
	"CylinderHandleAdornment",
	"CurveAnimation",
	"CornerWedgePart",
	"Configuration",
	"ConeHandleAdornment",
	"CompressorSoundEffect",
	"ColorCorrectionEffect",
	"Color3Value",
	"CollectionService",
	"Clouds",
	"Clothing",
	"ClimbController",
	"ClientReplicator",
	"ClickDetector",
	"ChorusSoundEffect",
	"ChatWindowConfiguration",
	"ChatInputBarConfiguration",
	"CharacterMesh",
	"CFrameValue",
	"CanvasGroup",
	"Camera",
	"BubbleChatConfiguration",
	"BrickColorValue",
	"BoxHandleAdornment",
	"BoolValue",
	"Bone",
	"BodyVelocity",
	"BodyThrust",
	"BodyPosition",
	"BodyMover",
	"BodyGyro",
	"BodyForce",
	"BodyColors",
	"BodyAngularEffect",
	"BlurEffect",
	"BloomEffect",
	"BlockMesh",
	"BindableFunction",
	"BindableEvent",
	"BillboardGui",
	"BevelMesh",
	"Beam",
	"PlayerGui",
	"Part",
	"BallSocketConstraint",
	"Tool",
	"Backpack",
	"Attachment",
	"Atmosphere",
	"AssetSoundEffect",
	"ArcHandles",
	"Animator",
	"AnimationController",
	"Animation",
	"AngularVelocity",
	"AlignPosition",
	"AlignOrientation",
	"AirController",
	"AdvancedDragger",
	"Actor",
	"Accessory",
	"Smoke",
	"SlidingBallConstraint",
	"Sky",
	"ServerStorage",
	"ServerScriptService",
	"SelectionSphere",
	"SelectionBox",
	"ScreenGui",
	"RotationCurve",
	"RopeConstraint",
	"RodConstraint",
	"RocketPropulsion",
	"ReverbSoundEffect",
	"ReplicatedStorage",
	"ReplicatedFirst",
	"RemoteFunction",
	"RemoteEvent",
	"QWidgetPluginGui",
	"ProximityPrompt",
	"PrismaticConstraint",
	"PointLight",
	"PluginGuiService",
	"PlayerScripts",
	"Players",
	"Player",
	"PlaneConstraint",
	"PitchShiftSoundEffect",
	"PathfindingModifier",
	"PathfindingLink",
	"UnionOperation",
	"ParticleEmitter",
	"ParabolaAdornment",
	"Pants",
	"PackageLink",
	"ObjectValue",
	"NumberValue",
	"NoCollisionConstraint",
	"NetworkServer",
	"NetworkClient",
	"NegateOperation",
	"ModuleScript",
	"Model",
	"LocalScript",
	"LineHandleAdornment",
	"LineForce",
	"LinearVelocity",
	"Lighting",
	"KeyframeSequence",
	"KeyframeMarker",
	"Keyframe",
	"IntValue",
	"IntConstrainedValue",
	"ImageLabel",
	"ImageHandleAdornment",
	"ImageButton",
	"IKControl",
	"HumanoidDescription",
	"Humanoid",
	"HingeConstraint",
	"Highlight",
	"Hat",
	"HandleAdornment",
	"Workspace",
	"WorldModel",
	"WireframeHandleAdornment",
	"WrapLayer",
	"WrapTarget",
	"WeldConstraint",
	"Weld",
	"WedgePart",
	"VoiceSource",
	"VoiceChatService",
	"VoiceChannel",
	"ViewportFrame",
	"VideoFrame",
	"VehicleSeat",
	"VehicleController",
	"VectorForce",
	"Vector3Value",
	"Vector3Curve",
	"UITextSizeConstraint",
	"UITableLayout",
	"UIStroke",
	"UISizeConstraint",
	"UIScale",
	"UIPageLayout",
	"UIPadding",
	"UIListLayout",
	"UIGridLayout",
	"UIGradient",
	"UICorner",
	"UIAspectRatioConstraint",
	"TrussPart",
	"TremoloSoundEffect",
	"Trail",
	"Texture",
	"TextSource",
	"TextLabel",
	"TextChatService",
	"TextChatConfigurations",
	"TextChatCommand",
	"TextChannel",
	"TextButton",
	"TextBox",
	"TestService",
	"TerrainRegion",
	"TerrainDetail",
	"Terrain",
	"Teams",
	"Team",
	"SwimController",
	"SurfaceSelection",
	"SurfaceLight",
	"SurfaceGui",
	"SurfaceAppearance",
	"SunRaysEffect",
	"StarterPlayerScripts",
	"StarterPlayer",
	"StarterPack",
	"StarterGui",
	"StarterCharacterScripts",
	"StandalonePluginScripts",
	"SpringConstraint",
	"SpotLight",
	"SphereHandleAdornment",
	"SpecialMesh",
	"SpawnLocation",
	"Sparkles",
	"SoundService",
	"SoundGroup",
	"Sound",
	"RayValue",
	"Message",
	"MeshPart",
	"MaterialVariant",
	"MaterialService",
	"MarkerCurve",
	"ManualWeld",
	"LSPFileSyncService",
	"LocalStorageService"
}


return {
	Input = {
		"ProximityPrompt",
		"ClickDetector",
		"DragDetector",
		"UIDragDetector",
		"InputAction",
		"InputBinding",
		"InputContext"
	},

	UI = {
		"SurfaceGui",
		"BillboardGui",
		"ScreenGui",
		"Frame",
		"TextLabel",
		"TextButton",
		"TextBox",
		"ImageLabel",
		"ImageButton",
		"ScrollingFrame",
		"CanvasGroup",
		"UICorner",
		"UIStroke",
		"UIGradient",
		"UIListLayout",
		"UIGridLayout",
		"UIPageLayout",
		"UITableLayout",
		"UIPadding",
		"UIScale",
		"UISizeConstraint",
		"UITextSizeConstraint",
		"UIAspectRatioConstraint",
		"ViewportFrame",
		"Path2D"
	},

	Chatting = {
		"TextChatCommand",
		"TextChannel"
	},

	Scripting = {
		"Script",
		"LocalScript",
		"ModuleScript",
		"RemoteEvent",
		"RemoteFunction",
		"BindableEvent",
		"BindableFunction",
		"Actor"
	},

	Value = {
		"ObjectValue",
		"StringValue",
		"IntValue",
		"NumberValue",
		"BoolValue",
		"Vector3Value",
		"CFrameValue",
		"Color3Value",
		"BrickColorValue",
		"RayValue"
	},

	Style = {
		"StyleSheet",
		"StyleRule",
		"StyleDerive",
		"StyleLink"
	},

	Parts = {
		"Part",
		"WedgePart",
		"CornerWedgePart",
		"TrussPart",
		"UnionOperation",
		"NegateOperation",
		"MeshPart"
	},

	Lighting = {
		"Camera",
		"PointLight",
		"SpotLight",
		"SurfaceLight",
		"Sky",
		"Atmosphere",
		"Clouds",
		"SunRaysEffect",
		"BloomEffect",
		"BlurEffect",
		"ColorCorrectionEffect",
		"DepthOfFieldEffect"
	},

	Constraints = {
		"Motor6D",
		"Weld",
		"AnimationConstraint",
		"AlignOrientation",
		"AlignPosition",
		"AngularVelocity",
		"Attachment",
		"BallSocketConstraint",
		"HingeConstraint",
		"PrismaticConstraint",
		"CylindricalConstraint",
		"RodConstraint",
		"RopeConstraint",
		"SpringConstraint",
		"WeldConstraint",
		"RigidConstraint",
		"NoCollisionConstraint",
		"UniversalConstraint"
	},

	Pathfinding = {
		"PathfindingLink",
		"PathfindingModifier"
	},

	Sounds = {
		"Sound",
		"SoundGroup",
		"ChorusSoundEffect",
		"CompressorSoundEffect",
		"DistortionSoundEffect",
		"EchoSoundEffect",
		"EqualizerSoundEffect",
		"FlangeSoundEffect",
		"PitchShiftSoundEffect",
		"ReverbSoundEffect",
		"TremoloSoundEffect",
	},

	Audio = {
		"AudioTextToSpeech",
		"AudioSpeechToText",
		"Wire",
		"AudioDeviceInput",
		"AudioDeviceOutput",
		"AudioPlayer",
		"AudioAnalyzer",
		"AudioChannelMixer",
		"AudioChannelSplitter",
		"AudioChorus",
		"AudioCompressor",
		"AudioEcho",
		"AudioDistortion",
		"AudioGate",
		"AudioLimiter",
		"AudioListener",
		"AudioEmitter",
		"AudioFilter",
		"AudioFlanger",
		"AudioReverb",
		"AudioPitchShifter",
		"AudioTremolo"
	},

	Meshes = {
		"SpecialMesh",
		"BlockMesh",
		"CylinderMesh",
		"FileMesh"
	},

	Textures = {
		"Texture",
		"Decal",
		"SurfaceAppearance",
		"MaterialVariant"
	},

	Gameplay = {
		"SpawnLocation",
		"Team",
		"Seat",
		"VehicleSeat",
		"Tool",
		"HopperBin",
		"Flag",
		"FlagStand",
		"Dialog",
		"DialogChoice"
	},

	Humanoid = {
		"Humanoid",
		"HumanoidDescription",
		"Accessory",
		"Hat",
		"Shirt",
		"Pants",
		"ShirtGraphic",
		"BodyColors",
		"BodyPartDescription",

		"ControllerManager",
		"ControllerPartSensor",
		"AirController",
		"ClimbController",
		"GroundController",
		"SwimController"
	},

	Animation = {
		"Animator",
		"Animation",
		"AnimationController",
		"Keyframe",
		"KeyframeSequence",
	},

	Grouping = {
		"Model",
		"Folder",
		"Configuration"
	},

	Effects = {
		"Explosion",
		"Fire",
		"Smoke",
		"Sparkles",
		"ParticleEmitter",
		"Trail",
		"Beam",
		"Highlight"
	},

	Adornments = {
		"SelectionBox",
		"SelectionSphere",
		"Handles",
		"ArcHandles",
		"SurfaceSelection"
	},

	Physics = {
		"LinearVelocity",
		"VectorForce",
		"LineForce",
		"WorldModel",
		"IKControl"
	},

	Sensors = {
		"AtmosphereSensor",
		"FluidForceSensor",
		"BuoyancySensor"
	},

	--Scriptable = {
	--	"TeleportOptions",
	--	"DataStoreOptions",
	--	"DataStoreGetOptions",
	--	"DataStoreSetOptions",
	--	"DataStoreIncrementOptions",
	--	"ExperienceInviteOptions",
	--	"AudioSearchParams",
	--}
} :: {[string]: {string}}


--!strict

local Selection = game:GetService("Selection")

local Types = require("../Types")
local UDF = require(script.Parent.UDF)
local Jecs = require("../Jecs")
local RawInst = require("./Small/RawInst")

local rawindex = RawInst.rawindex

local selectionVersion = 0

type Id = Jecs.Id
type World = Types.World
type ExplorerEntity = Types.ExplorerEntity
type IdMap = {[string]: Id}

type NoCondition = {
	type: "none"
}
type PairCondition = {
	type: "pair",
	component: string,
	value: any,
	rawValue: string
}
type HasCondition = {
	type: "has",
	componentName: string
}
type PropMatchesCondition = {
	type: "matches",
	property: string,
	pattern: string,
}
type PropIsCondition = {
	type: "is",
	property: string,
	expectedType: string
}
type PatternCondition = {
	type: "pattern",
	property: string,
	pattern: string
}
type PredicateCondition = {
	type: "predicate",
	propertyPath: string,
	component: string,
	value: any,
	rawValue: string
}
type DescendantCondition = {
	type: "descendant",
	condition: QueryCondition
}
type AncestorCondition = {
	type: "ancestor",
	condition: QueryCondition
}
type ParentCondition = {
	type: "parent",
	depth: number,
	wildcard: boolean,
	condition: QueryCondition
}
type FirstChildCondition = {
	type: "child",
	depth: number,
	wildcard: boolean,
	condition: QueryCondition
}
type AttributeCondition = {
	type: "attribute",
	attributeName: string,
	value: any,
	rawValue: string
}
type TagCondition = {
	type: "tag",
	name: string
}
type AndCondition = {
	type: "and",
	conditions: {QueryCondition}
}
type OrCondition = {
	type: "or",
	conditions: {QueryCondition}
}
type SelectedCondition = {
	type: "selected"
}
type NotCondition = {
	type: "not",
	condition: QueryCondition
}
type QueryCondition =
	|NoCondition
|PairCondition
|HasCondition
|PropMatchesCondition
|PropIsCondition
|PatternCondition
|PredicateCondition
|DescendantCondition
|AncestorCondition
|ParentCondition
|FirstChildCondition
|AttributeCondition
|TagCondition
|AndCondition
|OrCondition
|SelectedCondition
|NotCondition

local evaluateCondition

local JQL = {}

local function fuzzyMatch(str: string, pattern: string)
	if not str or not pattern then
		return false
	end

	local str = string.lower(tostring(str))
	local pattern = string.lower(tostring(pattern))

	return string.find(str, pattern, 1, true) ~= nil
end

local _error = error
local function error(msg: string, level: number?)
	_error({message  = msg}, level)
end

local function splitTopLevel(text: string, delimiter: string)
	local parts = {}
	local current = ""
	local depth = 0
	local i = 1

	while i <= #text do
		local char = text:sub(i, i)

		if char == "(" then
			depth += 1
			current = current .. char
		elseif char == ")" then
			depth -= 1
			current = current .. char
		elseif char == delimiter and depth == 0 then
			if current ~= "" then
				table.insert(parts, current)
				current = ""
			end
		else
			current = current .. char
		end

		i += 1
	end

	if current ~= "" then
		table.insert(parts, current)
	end

	return parts
end

local function parseInlinePair(expr: string): PairCondition
	local equalPos = nil
	local depth = 0
	for i = 1, #expr do
		local char = expr:sub(i, i)
		if char == "(" then
			depth += 1
		elseif char == ")" then
			depth -= 1
		elseif char == "=" and depth == 0 then
			equalPos = i
			break
		end
	end

	if not equalPos then
		error("Invalid pair syntax, missing '=': " .. expr)
	end

	local component = expr:sub(1, equalPos - 1):gsub("^%s+", ""):gsub("%s+$", "")
	local valueStr = expr:sub(equalPos + 1):gsub("^%s+", ""):gsub("%s+$", "")

	local parsedValue = UDF.from(valueStr)

	return {
		type = "pair",
		component = component,
		value = parsedValue,
		rawValue = valueStr
	}
end

local function parsePair(expr: string): PairCondition
	local content = expr:match("^%((.*)%)$")
	assert(content, `Invalid pair syntax: {expr}`)

	local equalPos = nil
	local depth = 0
	for i = 1, #content do
		local char = content:sub(i, i)
		if char == "(" then
			depth += 1
		elseif char == ")" then
			depth -= 1
		elseif char == "=" and depth == 0 then
			equalPos = i
			break
		end
	end

	if not equalPos then
		error("Invalid pair syntax, missing '=': " .. expr)
	end

	local component = content:sub(1, equalPos - 1):gsub("^%s+", ""):gsub("%s+$", "")
	local valueStr = content:sub(equalPos + 1):gsub("^%s+", ""):gsub("%s+$", "")

	local parsedValue = UDF.from(valueStr)

	return {
		type = "pair",
		component = component,
		value = parsedValue,
		rawValue = valueStr
	}
end

local function splitArguments(content: string): {string}
	local args = {}
	local current = ""
	local depth = 0
	local i = 1

	while i <= #content do
		local char = content:sub(i, i)

		if char == "(" then
			depth += 1
			current = current .. char
		elseif char == ")" then
			depth -= 1
			current = current .. char
		elseif char == "," and depth == 0 then
			local s = current:gsub("^%s+", ""):gsub("%s+$", "")
			table.insert(args, s)
			current = ""
		else
			current = current .. char
		end

		i += 1
	end

	if current ~= "" then
		local s = current:gsub("^%s+", ""):gsub("%s+$", "")
		table.insert(args, s)
	end

	return args
end

local function parseHasMacro(expr: string): HasCondition
	local content = expr:match("^@has%((.*)%)$")
	assert(content, `Invalid @has syntax: {expr}`)

	local componentName = content:gsub("^%s+", ""):gsub("%s+$", "")

	return {
		type = "has",
		componentName = componentName
	}
end

local function parseMatchesMacro(expr: string): PropMatchesCondition
	local content = expr:match("^@matches%((.*)%)$")
	assert(content, `Invalid @matches syntax: {expr}`)

	local parts = splitArguments(content)
	if #parts ~= 2 then
		error("@matches requires exactly 2 arguments: property and pattern: " .. expr)
	end

	local property = parts[1]
	local pattern = parts[2]

	return {
		type = "matches",
		property = property,
		pattern = pattern,
	}
end

local function parseIsMacro(expr: string): PropIsCondition
	local content = expr:match("^@is%((.*)%)$")
	assert(content, `Invalid @is syntax: {expr}`)

	local parts = splitArguments(content)
	if #parts ~= 2 then
		error("@is requires exactly 2 arguments: property and type: " .. expr)
	end

	return {
		type = "is",
		property = parts[1]:gsub("^%s+", ""):gsub("%s+$", ""),
		expectedType = parts[2]:gsub("^%s+", ""):gsub("%s+$", "")
	}
end

local function parsePatternMacro(expr: string): PatternCondition
	local content = expr:match("^@pattern%((.*)%)$")
	assert(content, `Invalid @pattern syntax: {content}`)

	local parts = splitArguments(content)
	if #parts ~= 2 then
		error("@pattern requires exactly 2 arguments: property and pattern string: " .. expr)
	end

	local property = parts[1]:gsub("^%s+", ""):gsub("%s+$", "")
	local patternStr = parts[2]:gsub("^%s+", ""):gsub("%s+$", "")

	local stripped = patternStr:match('^"(.*)"$') or patternStr:match("^'(.*)'$")
	assert(stripped, `@pattern second argument must be a quoted string: {expr}`)

	return {
		type = "pattern",
		property = property,
		pattern = stripped
	}
end

local function parsePredicate(expr: string): PredicateCondition
	local content = expr:match("^predicate%((.*)%)$")
	assert(content, `Invalid predicate syntax: {expr}`)

	local parts = splitArguments(content)
	if #parts ~= 3 then
		error("Predicate requires exactly 3 arguments: " .. expr)
	end

	local propertyPath = parts[1]
	local component = parts[2]
	local valueStr = parts[3]

	local parsedValue = UDF.from(valueStr)

	return {
		type = "predicate",
		propertyPath = propertyPath,
		component = component,
		value = parsedValue,
		rawValue = valueStr
	}
end

local function parseCondition(condition: string): QueryCondition
	local condition = condition
		:gsub("^%s+", "")
		:gsub("%s+$", "")

	-- Parse descendant operator (*<)
	if condition:match("^%*<") then
		local remaining = condition:sub(3)
		return {
			type = "descendant",
			condition = parseCondition(remaining)
		}
	end

	-- Parse ancestor operator (*>)
	if condition:match("^%*>") then
		local remaining = condition:sub(3)
		return {
			type = "ancestor",
			condition = parseCondition(remaining)
		}
	end

	-- Parse parent operators (< and <*)
	local parentDepth = 0
	local parentWildcard = false
	local remaining = condition

	if remaining:match("^<%*") then
		parentWildcard = true
		remaining = remaining:sub(3)
		parentDepth = 1
	else
		while remaining:match("^<") do
			parentDepth = parentDepth + 1
			remaining = remaining:sub(2)
		end
	end

	if parentDepth > 0 then
		return {
			type = "parent",
			depth = parentDepth,
			wildcard = parentWildcard,
			condition = parseCondition(remaining)
		}
	end

	-- Parse child operators (> and >*)
	local childDepth = 0
	local childWildcard = false
	remaining = condition

	if remaining:match("^>%*") then
		childWildcard = true
		remaining = remaining:sub(3)
		childDepth = 1
	else
		while remaining:match("^>") do
			childDepth = childDepth + 1
			remaining = remaining:sub(2)
		end
	end

	if childDepth > 0 then
		return {
			type = "child",
			depth = childDepth,
			wildcard = childWildcard,
			condition = parseCondition(remaining)
		}
	end

	-- Parse #NAME prefix (exact name match)
	if condition:match("^#[^?]") then
		local name = condition:sub(2):gsub("^%s+", ""):gsub("%s+$", "")
		return {
			type = "pair",
			component = "Name",
			value = name,
			rawValue = name
		}
	end

	-- Parse #?PARTIAL_NAME prefix (partial name match)
	if condition:match("^#%?") then
		local partialName = condition:sub(3):gsub("^%s+", ""):gsub("%s+$", "")
		return {
			type = "matches",
			property = "Name",
			pattern = partialName,
		}
	end

	-- Parse %CLASSNAME prefix (exact ClassName match)
	if condition:match("^%%[^?]") then
		local name = condition:sub(2):gsub("^%s+", ""):gsub("%s+$", "")
		return {
			type = "pair",
			component = "ClassName",
			value = name,
			rawValue = name
		}
	end

	-- Parse %?PARTIAL_CLASSNAME prefix (partial ClassName match)
	if condition:match("^%%%?") then
		local partialName = condition:sub(3):gsub("^%s+", ""):gsub("%s+$", "")
		return {
			type = "matches",
			property = "ClassName",
			pattern = partialName,
		}
	end

	-- Parse $ATTRIBUTE = VALUE (attribute query)
	if condition:match("^%$") then
		local equalPos = nil
		local depth = 0
		for i = 1, #condition do
			local char = condition:sub(i, i)
			if char == "(" then
				depth += 1
			elseif char == ")" then
				depth -= 1
			elseif char == "=" and depth == 0 then
				equalPos = i
				break
			end
		end

		if equalPos then
			local attributeName = condition:sub(2, equalPos - 1):gsub("^%s+", ""):gsub("%s+$", "")
			local valueStr = condition:sub(equalPos + 1):gsub("^%s+", ""):gsub("%s+$", "")
			local parsedValue = UDF.from(valueStr)

			return {
				type = "attribute",
				attributeName = attributeName,
				value = parsedValue,
				rawValue = valueStr
			}
		end
	end

	if condition:match("^predicate%(") then
		return parsePredicate(condition)
	elseif condition:match("^%(.*=.*%)$") then
		return parsePair(condition)
	elseif condition:match(".*=.*") then
		return parseInlinePair(condition)
	elseif condition:match("^%.") then
		local tagName = condition:sub(2):gsub("^%s+", ""):gsub("%s+$", "")
		return {
			type = "tag",
			name = tagName
		}
	elseif condition:match("^@has%(") then
		return parseHasMacro(condition)
	elseif condition:match("^@matches%(") then
		return parseMatchesMacro(condition)
	elseif condition:match("^@is%(") then
		return parseIsMacro(condition)
	elseif condition:match("^@pattern%(") then
		return parsePatternMacro(condition)
	elseif condition:match("^%[selected%]$") then
		return { type = "selected" }
	else
		error(`invalid condition: '{condition}'`)
	end
	return {
		type = "none"
	}
end

local function parseExpression(text: string): QueryCondition
	local text = text:gsub("^%s+", ""):gsub("%s+$", "")

	-- Parse ! (not) operator
	if text:match("^!") then
		local remaining = text:sub(2)
		return {
			type = "not",
			condition = parseExpression(remaining)
		}
	end

	local orParts = splitTopLevel(text, "|")
	if #orParts > 1 then
		local orConditions = {}
		for _, part in orParts do
			local subbed = part
				:gsub("^%s+", "")
				:gsub("%s+$", "")
			table.insert(orConditions, parseExpression(subbed))
		end
		return {
			type = "or",
			conditions = orConditions
		}
	end

	local andParts = splitTopLevel(text, "&")
	if #andParts > 1 then
		local andConditions = {}
		for _, part in andParts do
			local subbed = part
				:gsub("^%s+", "")
				:gsub("%s+$", "")
			table.insert(andConditions, parseExpression(subbed))
		end
		return {
			type = "and",
			conditions = andConditions
		}
	end

	if text:match("^%(.+%)$") then
		local content = text:match("^%((.+)%)$")
		assert(content, "Could not match parens")
		return parseExpression(content)
	end

	return parseCondition(text)
end

local function getQueryCacheKey(queryTree: QueryCondition): string
	if queryTree.type == "tag" then
		return "comp:" .. queryTree.name
	elseif queryTree.type == "pair" then
		return "pair:" .. queryTree.component .. "=" .. tostring(queryTree.value)
	elseif queryTree.type == "has" then
		return "has:" .. queryTree.componentName
	elseif queryTree.type == "matches" then
		return "matches:" .. queryTree.property .. "~" .. tostring(queryTree.pattern)
	elseif queryTree.type == "is" then
		return "is:" .. queryTree.property .. ":" .. queryTree.expectedType
	elseif queryTree.type == "pattern" then
		return "pattern:" .. queryTree.property .. "~" .. queryTree.pattern
	elseif queryTree.type == "attribute" then
		return "attr:" .. queryTree.attributeName .. "=" .. tostring(queryTree.value)
	elseif queryTree.type == "and" then
		local parts = {}
		for _, cond in queryTree.conditions do
			table.insert(parts, getQueryCacheKey(cond))
		end
		return "and(" .. table.concat(parts, ",") .. ")"
	elseif queryTree.type == "or" then
		local parts = {}
		for _, cond in queryTree.conditions do
			table.insert(parts, getQueryCacheKey(cond))
		end
		return "or(" .. table.concat(parts, ",") .. ")"
	elseif queryTree.type == "not" then
		return "not(" .. getQueryCacheKey(queryTree.condition) .. ")"
	elseif queryTree.type == "parent" then
		local wildcardStr = queryTree.wildcard and "*" or ""
		return "parent" .. wildcardStr .. queryTree.depth .. "(" .. getQueryCacheKey(queryTree.condition) .. ")"
	elseif queryTree.type == "child" then
		local wildcardStr = queryTree.wildcard and "*" or ""
		return "child" .. wildcardStr .. queryTree.depth .. "(" .. getQueryCacheKey(queryTree.condition) .. ")"
	elseif queryTree.type == "descendant" then
		return "descendant(" .. getQueryCacheKey(queryTree.condition) .. ")"
	elseif queryTree.type == "ancestor" then
		return "ancestor(" .. getQueryCacheKey(queryTree.condition) .. ")"
	elseif queryTree.type == "selected" then
		return "selected:" .. selectionVersion
	else
		return tostring(queryTree.type)
	end
end

local function intersectEntitySetsOptimized(set1: {ExplorerEntity}, set2: {ExplorerEntity}): {ExplorerEntity}
	if #set1 > #set2 then
		set1, set2 = set2, set1
	end

	local result = {}
	local set2Map = {}

	for _, entity in set2 do
		set2Map[entity] = true
	end

	for _, entity in set1 do
		if set2Map[entity] then
			table.insert(result, entity)
		end
	end

	return result
end

local function estimateConditionCost(condition: QueryCondition, world: World, componentMap: IdMap, tagMap: IdMap, attributeMap: IdMap)
	if condition.type == "tag" then
		local component = componentMap[condition.name] or tagMap[condition.name]
		if component then
			local count = 0
			for entity in world:query(component):iter() do
				count += 1
				if count > 1000 then break end
			end
			return count
		end
		return math.huge
	elseif condition.type == "has" then
		local component = componentMap[condition.componentName]
		if component then
			local count = 0
			for entity in world:query(component):iter() do
				count += 1
				if count > 1000 then break end
			end
			return count
		end
		return math.huge
	elseif condition.type == "matches" then
		return 5000
	elseif condition.type == "is" then
		return 5000
	elseif condition.type == "pattern" then
		return 5000
	elseif condition.type == "pair" then
		local component = componentMap[condition.component]
		if component then
			local count = 0
			for entity in world:query(component):iter() do
				count += 1
				if count > 500 then break end
			end
			return count * 0.1
		end
		return math.huge
	elseif condition.type == "attribute" then
		local attribute = attributeMap and attributeMap[condition.attributeName]
		if attribute then
			local count = 0
			for entity in world:query(attribute):iter() do
				count += 1
				if count > 500 then break end
			end
			return count * 0.1
		end
		return math.huge
	elseif condition.type == "not" then
		return math.huge
	elseif condition.type == "and" then
		local minCost = math.huge
		for _, subcond in condition.conditions do
			local cost = estimateConditionCost(subcond, world, componentMap, tagMap, attributeMap)
			minCost = math.min(minCost, cost)
		end
		return minCost
	elseif condition.type == "or" then
		local totalCost = 0
		for _, subcond in condition.conditions do
			local cost = estimateConditionCost(subcond, world, componentMap, tagMap, attributeMap)
			totalCost = totalCost + cost
		end
		return totalCost
	elseif condition.type == "descendant" or condition.type == "ancestor" then
		return 50000
	elseif condition.type == "child" or condition.type == "parent" then
		local baseCost = 10000
		if condition.wildcard then
			baseCost = baseCost * 5
		end
		return baseCost
	elseif condition.type == "selected" then
		return 10
	else
		error(`Unprocessed QueryCondition type: {condition.type}`)
	end
	return 0
end

local function sortConditionsBySelectivity(conditions: {QueryCondition}, world: World, componentMap: IdMap, tagMap: IdMap, attributeMap: IdMap)
	local conditionsWithCost = {}

	for i, condition in conditions do
		local cost = estimateConditionCost(condition, world, componentMap, tagMap, attributeMap)
		table.insert(conditionsWithCost, {condition = condition, cost = cost, index = i})
	end

	table.sort(conditionsWithCost, function(a, b)
		if a.cost == b.cost then
			return a.index < b.index
		end
		return a.cost < b.cost
	end)

	local sorted = {}
	for _, item in conditionsWithCost do
		table.insert(sorted, item.condition)
	end

	return sorted
end

local function getDescendantsAtDepthOptimized(world: World, entity: ExplorerEntity, depth: number)
	if depth == 0 then
		return {entity}
	end

	local descendants = {}
	local visited = {}

	local function collectAtDepth(currentEntity, currentDepth)
		if visited[currentEntity] or currentDepth > depth then
			return
		end
		visited[currentEntity] = true

		if currentDepth == depth then
			table.insert(descendants, currentEntity)
			return
		end
		local children = {}
		for child in world:children(currentEntity) do
			table.insert(children, child)
		end

		for _, child in children do
			collectAtDepth(child, currentDepth + 1)
		end
	end

	local rootChildren = {}
	for child in world:children(entity) do
		table.insert(rootChildren, child)
	end

	for _, child in rootChildren do
		collectAtDepth(child, 1)
	end

	return descendants
end

local function getAncestorAtDepthOptimized(world: World, entity: ExplorerEntity, depth: number): ExplorerEntity?
	local current = entity

	for i = 1, depth do
		local parent = world:parent(current)
		if not parent then
			return nil
		else
			current = parent
		end
	end

	return current
end

local function testConditionMatch(
	world: World, queryWorld: World, components: Types.Components, componentMap: IdMap, tagMap: IdMap, attributeMap: IdMap, entity: ExplorerEntity, condition: QueryCondition
)
	if condition.type == "tag" then
		local component = componentMap[condition.name] or tagMap[condition.name]
		if not component then return false end
		return world:has(entity, component)
	elseif condition.type == "pair" then
		local component = componentMap[condition.component]
		if not component then return false end

		if not world:has(entity, component) then return false end

		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = rawindex(instance, condition.component)
		return condition.value == value
	elseif condition.type == "has" then
		local component = componentMap[condition.componentName]
		if not component then return false end
		return world:has(entity, component)
	elseif condition.type == "matches" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = rawindex(instance, condition.property)
		if not value then return false end

		return fuzzyMatch(value, condition.pattern)
	elseif condition.type == "is" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = rawindex(instance, condition.property)
		if value == nil then return false end

		local enumType = condition.expectedType:match("^Enum%.(.+)$")
		if enumType then
			return typeof(value) == "EnumItem" and tostring(value.EnumType) == enumType
		end

		return typeof(value) == condition.expectedType
	elseif condition.type == "pattern" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = rawindex(instance, condition.property)
		if value == nil then return false end

		return tostring(value):match(condition.pattern) ~= nil
	elseif condition.type == "attribute" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = instance:GetAttribute(condition.attributeName)
		return condition.value == value
	elseif condition.type == "selected" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local selection = Selection:Get()
		if table.find(selection, instance) then return true end
		return false
	elseif condition.type == "not" then
		return not testConditionMatch(world, queryWorld, components, componentMap, tagMap, attributeMap, entity, condition.condition)
	else
		local result = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, condition)
		for _, resultEntity in result do
			if resultEntity == entity then
				return true
			end
		end
		return false
	end
end

local function getAllWildcardParents(
	world: World, queryWorld: World, components: Types.Components, componentMap: IdMap, tagMap: IdMap, attributeMap: IdMap, entity: ExplorerEntity, condition: QueryCondition
)
	local matches = {}
	local current = world:parent(entity)
	local visited = {}

	while current and not visited[current] do
		visited[current] = true

		if testConditionMatch(world, queryWorld, components, componentMap, tagMap, attributeMap, current, condition) then
			table.insert(matches, current)
		end

		current = world:parent(current)
	end

	return matches
end

local function getAllWildcardChildren(
	world: World, queryWorld: World, components: Types.Components, componentMap: IdMap, tagMap: IdMap, attributeMap: IdMap, entity: ExplorerEntity, condition: QueryCondition
)
	local matches = {}
	local visited = {}

	local function searchChildren(currentEntity)
		if visited[currentEntity] then
			return
		end
		visited[currentEntity] = true

		local children = {}
		for child in world:children(currentEntity) do
			table.insert(children, child)
		end

		for _, child in children do
			if testConditionMatch(world, queryWorld, components, componentMap, tagMap, attributeMap, child, condition) then
				table.insert(matches, child)
			end
			searchChildren(child)
		end
	end

	searchChildren(entity)
	return matches
end

local function checkSingleCondition(
	world: World, components: Types.Components, componentMap: IdMap, attributeMap: IdMap, entity: ExplorerEntity, condition: QueryCondition
)
	if condition.type == "tag" then
		return true
	elseif condition.type == "has" then
		return true
	elseif condition.type == "matches" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = rawindex(instance, condition.property)
		if not value then return false end

		return fuzzyMatch(value, condition.pattern)
	elseif condition.type == "is" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = rawindex(instance, condition.property)
		if value == nil then return false end

		local enumType = condition.expectedType:match("^Enum%.(.+)$")
		if enumType then
			return typeof(value) == "EnumItem" and tostring(value.EnumType) == enumType
		end

		return typeof(value) == condition.expectedType
	elseif condition.type == "pattern" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = rawindex(instance, condition.property)
		if value == nil then return false end

		return tostring(value):match(condition.pattern) ~= nil
	elseif condition.type == "pair" then
		local component = componentMap[condition.component]
		if not component then
			return false
		end

		local instance = world:get(entity, components.Instance)
		if not instance then return false end
		local value = rawindex(instance, condition.component)

		return condition.value == value
	elseif condition.type == "attribute" then
		local instance = world:get(entity, components.Instance)
		if not instance then return false end

		local value = instance:GetAttribute(condition.attributeName)
		return condition.value == value
	end

	return false
end

local function evaluateLeafCondition(
	world: World, queryWorld: World, components: Types.Components, componentMap: IdMap, tagMap: IdMap, attributeMap: IdMap, condition: QueryCondition
)
	local validEntities = {}

	if condition.type == "matches" or condition.type == "is" or condition.type == "pattern" then
		local instanceComponent = components.Instance
		if not instanceComponent then
			return validEntities
		end

		for entity in world:query(instanceComponent):iter() do
			if checkSingleCondition(world, components, componentMap, attributeMap, entity, condition) then
				table.insert(validEntities, entity)
			end
		end

		return validEntities
	elseif condition.type == "selected" then
		local selection = game:GetService("Selection"):Get()
		local selectionSet = {}
		for _, inst in selection do
			selectionSet[inst] = true
		end
		for entity in world:query(components.Instance):iter() do
			local instance = world:get(entity, components.Instance)
			if instance and selectionSet[instance] then
				table.insert(validEntities, entity)
			end
		end
		return validEntities
	end

	local queriedComponents = {}

	if condition.type == "tag" then
		local component = tagMap[condition.name]
		if component then
			table.insert(queriedComponents, component)
		end

	elseif condition.type == "has" then
		local component = componentMap[condition.componentName]
		if component then
			table.insert(queriedComponents, component)
		end

	elseif condition.type == "pair" then
		local component = componentMap[condition.component]
		if component then
			table.insert(queriedComponents, component)
		end

	elseif condition.type == "attribute" then
		local attribute = attributeMap[condition.attributeName]
		if attribute then
			table.insert(queriedComponents, attribute)
		end
	end

	if #queriedComponents == 0 then
		return validEntities
	end

	local query = queryWorld:query(unpack(queriedComponents))
	local entityList = {}

	for queryEntity in query:iter() do
		table.insert(entityList, queryEntity)
	end

	for _, queryEntity in entityList do
		local entities = queryWorld:get(queryEntity, components.QueryLinkedEntities)
		if not entities then continue end

		for _, entity in entities do
			if checkSingleCondition(world, components, componentMap, attributeMap, entity, condition) then
				table.insert(validEntities, entity)
			end
		end
	end

	return validEntities
end

function evaluateCondition(
	world: World, queryWorld: World, components: Types.Components, componentMap: IdMap, tagMap: IdMap, attributeMap: IdMap, condition: QueryCondition
)
	if condition.type == "not" then
		local allEntities = {}
		for entity in world:query(components.Instance):iter() do
			table.insert(allEntities, entity)
		end

		local excluded = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, condition.condition)
		local excludedSet = {}
		for _, entity in excluded do
			excludedSet[entity] = true
		end

		local result = {}
		for _, entity in allEntities do
			if not excludedSet[entity] then
				table.insert(result, entity)
			end
		end
		return result
	elseif condition.type == "and" then
		local sortedConditions = sortConditionsBySelectivity(condition.conditions, world, componentMap, tagMap, attributeMap)

		local result = nil
		for _, subCondition in sortedConditions do
			local subResult = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, subCondition)

			if result == nil then
				result = subResult
			else
				result = intersectEntitySetsOptimized(result, subResult)
			end

			if #result == 0 then
				return {}
			end
		end
		return result or {}
	elseif condition.type == "or" then
		local resultSet = {}
		local seen = {}

		for _, subCondition in condition.conditions do
			local subResult = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, subCondition)
			for _, entity in subResult do
				if not seen[entity] then
					seen[entity] = true
					table.insert(resultSet, entity)
				end
			end
		end
		return resultSet
	elseif condition.type == "parent" then
		local validEntities = {}

		if condition.wildcard then
			local leafEntities = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap,
				{type = "tag", name = (condition.condition :: any).name or (condition.condition :: any).component or "Instance"})

			for _, entity in leafEntities do
				local parentMatches = getAllWildcardParents(world, queryWorld, components, componentMap, tagMap, attributeMap, entity, condition.condition)
				for _, parentEntity in parentMatches do
					table.insert(validEntities, entity)
					break
				end
			end
		else
			local parentMatches = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, condition.condition)
			for _, parentEntity in parentMatches do
				local descendants = getDescendantsAtDepthOptimized(world, parentEntity, condition.depth)
				for _, descendant in descendants do
					table.insert(validEntities, descendant)
				end
			end
		end

		return validEntities
	elseif condition.type == "child" then
		local validEntities = {}

		if condition.wildcard then
			local leafEntities = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap,
				{type = "tag", name = (condition.condition :: any).name or (condition.condition :: any).component or "Instance"})

			for _, entity in leafEntities do
				local childMatches = getAllWildcardChildren(world, queryWorld, components, componentMap, tagMap, attributeMap, entity, condition.condition)
				for _, childEntity in childMatches do
					table.insert(validEntities, entity)
					break
				end
			end
		else
			local childMatches = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, condition.condition)
			for _, childEntity in childMatches do
				local ancestor = getAncestorAtDepthOptimized(world, childEntity, condition.depth)
				if ancestor then
					table.insert(validEntities, ancestor)
				end
			end
		end

		return validEntities
	elseif condition.type == "descendant" then
		local validEntities = {}
		local ancestorMatches = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, condition.condition)

		for _, ancestorEntity in ancestorMatches do
			local descendants = JQL.getAllChildren(world, ancestorEntity)
			for _, descendant in descendants do
				table.insert(validEntities, descendant)
			end
		end

		return validEntities
	elseif condition.type == "ancestor" then
		local validEntities = {}
		local descendantMatches = evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, condition.condition)
		local addedEntities = {}

		for _, descendantEntity in descendantMatches do
			local current = world:parent(descendantEntity)
			local visited = {}

			while current and not visited[current] do
				visited[current] = true

				if not addedEntities[current] then
					addedEntities[current] = true
					table.insert(validEntities, current)
				end

				current = world:parent(current)
			end
		end

		return validEntities
	else
		return evaluateLeafCondition(world, queryWorld, components, componentMap, tagMap, attributeMap, condition)
	end
end

function JQL.executeQuery(world: World, queryWorld: World, components: Types.Components, componentMap: IdMap, tagMap: IdMap, attributeMap: IdMap, condition: QueryCondition)
	return evaluateCondition(world, queryWorld, components, componentMap, tagMap, attributeMap or {}, condition)
end

function JQL.parseQuery(queryText: string): QueryCondition
	return parseExpression(queryText)
end

local parentTreeCache = {}
local cacheSize = 0
local MAX_CACHE_SIZE = 1000

function JQL.collectParentTree(world: World, entity: ExplorerEntity, visited: {[ExplorerEntity]: boolean}?, useCache: boolean?): {ExplorerEntity}
	local useCache = useCache ~= false

	if useCache and parentTreeCache[entity] then
		return parentTreeCache[entity]
	end

	local visited = visited or {}
	local tree = {}

	if visited[entity] then
		return tree
	end
	visited[entity] = true

	table.insert(tree, entity)

	local parent = world:parent(entity)
	if parent then
		local parentTree = JQL.collectParentTree(world, parent, visited, useCache)
		for _, parentEntity in parentTree do
			table.insert(tree, parentEntity)
		end
	end

	if useCache and cacheSize < MAX_CACHE_SIZE then
		parentTreeCache[entity] = tree
		cacheSize = cacheSize + 1
	end

	return tree
end

function JQL.clearParentTreeCache()
	parentTreeCache = {}
	cacheSize = 0
end

function JQL.getAllChildren(world: World, entity: ExplorerEntity, maxDepth: number?)
	local children = {}
	local visited = {}
	local maxDepth = maxDepth or 100

	local function collectChildren(currentEntity, depth)
		if visited[currentEntity] or depth > maxDepth then
			return
		end
		visited[currentEntity] = true
		for child in world:children(currentEntity) do
			table.insert(children, child)
			collectChildren(child, depth + 1)
		end
	end

	collectChildren(entity, 0)
	return children
end

function JQL.isAncestorOf(world: World, potentialAncestor: ExplorerEntity, entity: ExplorerEntity, maxDepth: number?)
	local maxDepth = maxDepth or 1000
	local current = entity
	local depth = 0

	while current and depth < maxDepth do
		local parent = world:parent(current)
		if parent then
			current = parent
		end
		depth = depth + 1

		if current == potentialAncestor then
			return true
		end
	end

	return false
end

function JQL.isOnPathToValidEntity(world: World, entity: ExplorerEntity, validEntitySet: {[ExplorerEntity]: boolean})
	if validEntitySet[entity] then
		return true
	end

	local current = world:parent(entity)
	local depth = 0
	local maxDepth = 50

	while current and depth < maxDepth do
		if validEntitySet[current] then
			return true
		end
		current = world:parent(current)
		depth = depth + 1
	end

	return false
end

function JQL.bumpSelectionVersion()
	selectionVersion += 1
end

return JQL


local cst = require("./cst")

local function unreachable(_: never): never
	error("unreachable")
end

local span_generic_declaration: (cst.GenericDeclaration) -> cst.Span
local span_generic_declaration_with_defaults: (cst.GenericDeclarationWithDefaults) -> cst.Span
local span_type_arg: (cst.TypeArg) -> cst.Span
local span_table_type_field: (cst.TableTypeField) -> cst.Span
local span_tail_type: (cst.TailType) -> cst.Span
local span_type: (cst.Type) -> cst.Span
local span_type_pack: (cst.TypePack) -> cst.Span
local span_return_type: (cst.ReturnType) -> cst.Span
local span_binding: (cst.Binding) -> cst.Span
local span_istring: (cst.IString) -> cst.Span
local span_if_else_expr_branch: (cst.IfElseExprBranch) -> cst.Span
local span_expr: (cst.Expr) -> cst.Span
local span_function_arg: (cst.FunctionArg) -> cst.Span
local span_var_root: (cst.VarRoot) -> cst.Span
local span_var_suffix: (cst.VarSuffix) -> cst.Span
local span_var: (cst.Var) -> cst.Span
local span_table_field: (cst.TableField) -> cst.Span
local span_table: (cst.Table) -> cst.Span
local span_function_body: (cst.FunctionBody) -> cst.Span
local span_if_stat_branch: (cst.IfStatBranch) -> cst.Span
local span_stat: (cst.Stat) -> cst.Span
local span_last_stat: (cst.LastStat) -> cst.Span
local span_block: (cst.Block) -> cst.Span

local function merge(left: cst.Span, right: cst.Span): cst.Span
	return vector.create(left.x, right.x)
end

function span_generic_declaration(node: cst.GenericDeclaration): cst.Span
	local angles = node.angles
	return merge(angles.open.span, angles.close.span)
end

function span_generic_declaration_with_defaults(node: cst.GenericDeclarationWithDefaults): cst.Span
	local angles = node.angles
	return merge(angles.open.span, angles.close.span)
end

function span_type_arg(node: cst.TypeArg): cst.Span
	if
		node.kind == "variadic"
		or node.kind == "generic"
		or node.kind == "list"
	then
		return span_type_pack(node)
	else
		return span_type(node)
	end
end

function span_table_type_field(node: cst.TableTypeField): cst.Span
	local access = node.access

	if access == nil then
		local span: cst.Span

		if node.kind == "nameprop" then
			span = node.name.span
		elseif node.kind == "stringprop" or node.kind == "indexer" then
			span = node.bracks.open.span
		else
			unreachable(node.kind)
		end

		return merge(span, span_type(node.type))
	else
		return merge(access.span, span_type(node.type))
	end
end

function span_tail_type(node: cst.TailType): cst.Span
	return span_type_pack(node)
end

function span_type(node: cst.Type): cst.Span
	if
		node.kind == "nil"
		or node.kind == "boolean"
		or node.kind == "string"
	then
		return node.tok.span
	elseif node.kind == "reference" then
		local prefix = node.prefix
		local generics = node.generics

		return merge(
			if prefix then prefix.name.span else node.name.span,
			if generics then generics.angles.close.span else node.name.span
		)
	elseif node.kind == "typeof" then
		return merge(
			node.typeof_tok.span,
			node.parens.close.span
		)
	elseif node.kind == "array" or node.kind == "table" then
		return merge(
			node.braces.open.span,
			node.braces.close.span
		)
	elseif node.kind == "function" then
		return merge(
			node.parens.open.span,
			span_return_type(node.ret)
		)
	elseif node.kind == "paren" then
		node = node :: cst.Type_Paren -- Luau Bug
		local parens = node.parens

		return merge(
			parens.open.span,
			parens.close.span
		)
	elseif node.kind == "optional" then
		return merge(
			span_type(node.type),
			node.question.span
		)
	elseif node.kind == "union" or node.kind == "intersection" then
		node = node :: cst.Type_Union | cst.Type_Intersection -- Luau Bug

		local leading = node.leading
		local last = node.types[(#node.types :: any) :: number]

		return merge(
			if leading then leading.span else span_type(node.types[1].node),
			span_type(last.node)
		) :: cst.Span
	else
		return unreachable(node.kind)
	end
end

function span_type_pack(node: cst.TypePack): cst.Span
	if node.kind == "variadic" then
		return merge(
			node.dots.span,
			span_type(node.type)
		)
	elseif node.kind == "generic" then
		return merge(
			node.name.span,
			node.dots.span
		)
	elseif node.kind == "list" then
		local parens = node.parens
		return merge(
			parens.open.span,
			parens.close.span
		)
	else
		return unreachable(node.kind)
	end
end

function span_return_type(node: cst.ReturnType): cst.Span
	if
		node.kind == "variadic"
		or node.kind == "generic"
		or node.kind == "list"
	then
		return span_type_pack(node)
	else
		return span_type(node)
	end
end

function span_binding(node: cst.Binding): cst.Span
	local type = node.type
	if type then
		return merge(
			node.name.span,
			span_type(type.type)
		)
	else
		return node.name.span
	end
end

function span_istring(node: cst.IString): cst.Span
	if node.kind == "simple" then
		return node.tok.span
	elseif node.kind == "complex" then
		local first = node.first
		local last = node.last

		return merge(
			first.span,
			last.span
		)
	else
		return unreachable(node.kind)
	end
end

function span_if_else_expr_branch(node: cst.IfElseExprBranch): cst.Span
	return merge(
		span_expr(node.condition),
		span_expr(node.body)
	)
end

function span_expr(node: cst.Expr): cst.Span
	if
		node.kind == "nil"
		or node.kind == "boolean"
		or node.kind == "number"
		or node.kind == "string"
		or node.kind == "varargs"
	then
		return node.tok.span
	elseif node.kind == "istring" then
		return span_istring(node.istring)
	elseif node.kind == "table" then
		return span_table(node.table)
	elseif node.kind == "function" then
		local attribute = node.attributes[1]

		if attribute then
			return merge(
				attribute.span,
				span_function_body(node.body)
			)
		else
			return merge(
				node.function_tok.span,
				span_function_body(node.body)
			)
		end
	elseif node.kind == "ifelse" then
		node = node :: cst.Expr_IfElse -- Luau Bug

		return merge(
			node.if_tok.span,
			span_expr(node.else_body)
		)
	elseif node.kind == "var" then
		return span_var(node.var)
	elseif node.kind == "assertion" then
		node = node :: cst.Expr_Assertion -- Luau Bug

		return merge(
			span_expr(node.expr),
			span_type(node.type)
		)
	elseif node.kind == "unary" then
		return merge(
			node.operator.span,
			span_expr(node.expr)
		)
	elseif node.kind == "binary" then
		node = node :: cst.Expr_Binary -- Luau Bug

		return merge(
			span_expr(node.left),
			span_expr(node.right)
		)
	else
		return unreachable(node.kind)
	end
end

function span_function_arg(node: cst.FunctionArg): cst.Span
	if node.kind == "pack" then
		local parens = node.parens
		return merge(
			parens.open.span,
			parens.close.span
		)
	elseif node.kind == "table" then
		return span_table(node.table)
	elseif node.kind == "string" then
		return node.tok.span
	else
		return unreachable(node.kind)
	end
end

function span_var_root(node: cst.VarRoot): cst.Span
	if node.kind == "name" then
		return node.tok.span
	elseif node.kind == "paren" then
		return merge(
			node.parens.open.span,
			node.parens.close.span
		)
	else
		return unreachable(node.kind)
	end
end

function span_var_suffix(node: cst.VarSuffix): cst.Span
	if node.kind == "nameindex" then
		return merge(
			node.dot.span,
			node.name.span
		)
	elseif node.kind == "exprindex" then
		local bracks = node.bracks
		return merge(
			bracks.open.span,
			bracks.close.span
		)
	elseif node.kind == "call" then
		local method = node.method
		local arg = node.arg

		if method then
			return merge(
				method.colon.span,
				span_function_arg(arg)
			)
		else
			return span_function_arg(arg)
		end
	else
		return unreachable(node.kind)
	end
end

function span_var(node: cst.Var): cst.Span
	local root = node.root
	local last = node.suffixes[(#node.suffixes :: any) :: number]

	if last then
		return merge(
			span_var_root(root),
			span_var_suffix(last)
		)
	else
		return span_var_root(root)
	end
end

function span_table_field(node: cst.TableField): cst.Span
	if node.kind == "namekey" then
		return merge(
			node.name.span,
			span_expr(node.value)
		)
	elseif node.kind == "exprkey" then
		local bracks = node.bracks
		return merge(
			bracks.open.span,
			span_expr(node.value)
		)
	elseif node.kind == "nokey" then
		return span_expr(node.value)
	else
		return unreachable(node.kind)
	end
end

function span_table(node: cst.Table): cst.Span
	local braces = node.braces
	return merge(
		braces.open.span,
		braces.close.span
	)
end

function span_function_body(node: cst.FunctionBody): cst.Span
	local generics = node.generics

	if generics then
		return merge(
			generics.angles.open.span,
			node.end_tok.span
		)
	else
		return merge(
			node.parens.open.span,
			node.end_tok.span
		)
	end
end

function span_if_stat_branch(node: cst.IfStatBranch): cst.Span
	local block = node.block

	if (#block.stats :: any) == 0 then
		return merge(
			span_expr(node.condition),
			node.then_tok.span
		)
	else
		return merge(
			span_expr(node.condition),
			span_block(block)
		)
	end
end

function span_stat(node: cst.Stat): cst.Span
	if node.kind == "assign" then
		local first = node.left[1]
		local last = node.right[(#node.right :: any) :: number]

		return merge(
			span_var(first.node),
			span_expr(last.node)
		)
	elseif node.kind == "compoundassign" then
		node = node :: cst.Stat_CompoundAssign -- Luau Bug

		return merge(
			span_var(node.left),
			span_expr(node.right)
		)
	elseif node.kind == "call" then
		return span_var(node.call)
	elseif node.kind == "do" then
		return merge(
			node.do_tok.span,
			node.end_tok.span
		)
	elseif node.kind == "while" then
		return merge(
			node.while_tok.span,
			node.end_tok.span
		)
	elseif node.kind == "repeat" then
		return merge(
			node.repeat_tok.span,
			span_expr(node.condition)
		)
	elseif node.kind == "if" then
		return merge(
			node.if_tok.span,
			node.end_tok.span
		)
	elseif node.kind == "numericfor" or node.kind == "forin" then
		return merge(
			node.for_tok.span,
			node.end_tok.span
		)
	elseif node.kind == "function" then
		local attribute = node.attributes[1]

		if attribute then
			return merge(
				attribute.span,
				span_function_body(node.body)
			)
		else
			return merge(
				node.function_tok.span,
				span_function_body(node.body)
			)
		end
	elseif node.kind == "localfunction" then
		node = node :: cst.Stat_LocalFunction -- Luau Bug

		local attribute = node.attributes[1]

		if attribute then
			return merge(
				attribute.span,
				span_function_body(node.body)
			)
		else
			return merge(
				node.local_tok.span,
				span_function_body(node.body)
			)
		end
	elseif node.kind == "localvariable" then
		local values = node.values

		if values then
			local exprs = values.exprs
			local last = exprs[(#exprs :: any) :: number]

			return merge(
				node.local_tok.span,
				span_expr(last.node)
			)
		else
			local bindings = node.bindings
			local last = bindings[(#bindings :: any) :: number]

			return merge(
				node.local_tok.span,
				span_binding(last.node)
			)
		end
	elseif node.kind == "type" then
		node = node :: cst.Stat_Type -- Luau Bug

		local export_tok = node.export_tok

		if export_tok then
			return merge(
				export_tok.span,
				span_type(node.type)
			)
		else
			return merge(
				node.type_tok.span,
				span_type(node.type)
			)
		end
	else
		return unreachable(node.kind)
	end
end

function span_last_stat(node: cst.LastStat): cst.Span
	if node.kind == "return" then
		local exprs = node.exprs
		local last = exprs[(#exprs :: any) :: number]

		return merge(
			node.return_tok.span,
			span_expr(last.node)
		)
	elseif node.kind == "break" or node.kind == "continue" then
		return node.tok.span
	else
		return unreachable(node.kind)
	end
end

function span_block(node: cst.Block): cst.Span
	local stats = node.stats
	local first = stats[1]
	local last_stat = node.last_stat

	if first == nil and last_stat == nil then
		error("attempt to get the span of an empty block")
	end

	if first == nil and last_stat ~= nil then
		local semicolon = last_stat.semicolon

		if semicolon then
			return merge(
				span_last_stat(last_stat.stat),
				semicolon.span
			)
		else
			return span_last_stat(last_stat.stat)
		end
	elseif last_stat ~= nil then
		local semicolon = last_stat.semicolon

		if semicolon then
			return merge(
				span_stat(first.node),
				semicolon.span
			)
		else
			return merge(
				span_stat(first.node),
				span_last_stat(last_stat.stat)
			)
		end
	else
		local last = stats[(#stats :: any) :: number]
		local sep = last.sep

		if sep then
			return merge(
				span_stat(first.node),
				sep.span
			)
		else
			return merge(
				span_stat(first.node),
				span_stat(last.node)
			)
		end
	end
end

return {
	generic_declaration = span_generic_declaration,
	generic_declaration_with_defaults = span_generic_declaration_with_defaults,
	type_arg = span_type_arg,
	table_type_field = span_table_type_field,
	tail_type = span_tail_type,
	type = span_type,
	type_pack = span_type_pack,
	return_type = span_return_type,
	binding = span_binding,
	istring = span_istring,
	if_else_expr_branch = span_if_else_expr_branch,
	expr = span_expr,
	function_arg = span_function_arg,
	var_root = span_var_root,
	var_suffix = span_var_suffix,
	var = span_var,
	table_field = span_table_field,
	table = span_table,
	function_body = span_function_body,
	if_stat_branch = span_if_stat_branch,
	stat = span_stat,
	last_stat = span_last_stat,
	block = span_block,
}


local cst = require("./cst")
local visit = require("./visit")

local module = {}

do
	local visitor = visit.visitor()

	local buf = buffer.create(1024)
	local pos = 0

	local function write(string: string)
		local bytes = #string
		local new_pos = (pos + bytes) :: number
		local old_len = buffer.len(buf)

		if new_pos >= old_len then
			local new_len = old_len * 2
			while new_pos >= new_len do
				new_len = new_len * 2
			end

			local new_buf = buffer.create(new_len)
			buffer.copy(new_buf, 0, buf, 0, pos)
			buf = new_buf
		end

		buffer.writestring(buf, pos, string, bytes)
		pos = new_pos
	end

	function visitor.visit_token(_, token)
		for _, trivia in token.trivia do
			write(trivia.text)
		end

		if (token :: any).text then
			write((token :: any).text :: any)
		elseif token.kind ~= "eof" then
			write(token.kind)
		end
	end

	function module.tostring(cst: cst.Cst)
		pos = 0
		visit.visit(visitor, nil, cst)
		return buffer.readstring(buf, 0, pos)
	end
end

do
	local visitor = visit.visitor()

	local lines: { string } = {}

	for key in visitor :: any do
		(visitor :: any)[key] = function()
			table.insert(lines, key)
		end
	end

	function module.visited(cst: cst.Cst)
		table.clear(lines)
		visit.visit(visitor, nil, cst)
		return table.concat(lines, "\n")
	end
end

return module