## Scanner to find inheriting classes. Used to detect inheritors of
## modifiers and triggers. Ideally this would be built into the editor
## but sometimes one has to hack their way around the limitations.
## This only scans to the extent needed to drive the UI, it's not a general
## purpose implementation.
@tool

const GUIDESet = preload("../guide_set.gd")

var _dirty:bool = true

# looks like we only get very limited access to the script's inheritance tree,
# so we need to do a little caching ourselves
var _script_lut:Dictionary = {}

func _init():
	EditorInterface.get_resource_filesystem().script_classes_updated.connect(_mark_dirty)


func _mark_dirty():
	_dirty = true

## Returns all classes that directly or indirectly inherit from the 
## given class. Only works for scripts in the project, e.g. doesn't
## scan the whole class_db. Key is class name, value is the Script instance
func find_inheritors(clazz_name:StringName) -> Dictionary:
	var result:Dictionary = {}

	var root := EditorInterface.get_resource_filesystem().get_filesystem()
	
	# rebuild the LUT when needed
	if _dirty:
		_script_lut.clear()
		_scan(root)
		_dirty = false
		
	
	var open_set:GUIDESet = GUIDESet.new()
	# a closed set just to avoid infinite loops, we'll never
	# look at the same class more than once.
	var closed_set:GUIDESet = GUIDESet.new()
	
	open_set.add(clazz_name)
	
	while not open_set.is_empty():
		var next = open_set.pull()
		closed_set.add(next)
		if not _script_lut.has(next):
			# we don't know this script, ignore, move on
			continue
		
		# now find all scripts that extend the one we 
		# are looking at
		for item:ScriptInfo in _script_lut.values():
			if item.extendz == next:
				# put them into the result
				result[item.clazz_name] = item.clazz_script
				# and put their class in the open set
				# unless we already looked at it.
				if not closed_set.has(item.clazz_name):
					open_set.add(item.clazz_name)
		
	return result


func _scan(folder:EditorFileSystemDirectory):
	for i in folder.get_file_count():
		var script_clazz = folder.get_file_script_class_name(i)
		if script_clazz != "":
			var info := _script_lut.get(script_clazz)
			if info == null:
				info = ScriptInfo.new()
				info.clazz_name = script_clazz
				info.clazz_script = ResourceLoader.load(folder.get_file_path(i))
				_script_lut[script_clazz] = info
				
			var script_extendz = folder.get_file_script_class_extends(i)
			info.extendz = script_extendz
			
	for i in folder.get_subdir_count():
		_scan(folder.get_subdir(i))		
					
	
class ScriptInfo:
	var clazz_name:StringName
	var extendz:StringName	
	var clazz_script:Script
	
	func _to_string() -> String:
		return clazz_name + ":" + extendz
	
