class_name M2NativeAnimationResourceObserver
extends RefCounted

## Resolves the existing native GryphonRoost animated M2 prototype path.

const M2_NATIVE_ANIMATED_BUILDER_SCRIPT := preload(
	"res://addons/mpq_extractor/loaders/m2_native_animated_builder.gd"
)

var _native_animated_builder: Object


func _init(native_animated_builder: Object = M2_NATIVE_ANIMATED_BUILDER_SCRIPT) -> void:
	_native_animated_builder = native_animated_builder


## Returns whether the normalized path uses the historical native animation path.
func is_native_animation_candidate(normalized_relative_path: String) -> bool:
	return normalized_relative_path.to_lower().contains("gryphonroost")


## Returns the exact cached or newly adopted native animated prototype.
## Rejected native candidates are marked static-only for the renderer session.
func observe(
	normalized_relative_path: String,
	extracted_directory: String,
	raw_model_repository: RefCounted,
	prototype_cache_state: RefCounted,
	debug_logging_enabled: bool = false
) -> Node3D:
	if (
		normalized_relative_path.is_empty()
		or not is_native_animation_candidate(normalized_relative_path)
		or raw_model_repository == null
		or prototype_cache_state == null
	):
		return null

	var cached_prototype := prototype_cache_state.call(
		"find_animated_prototype",
		normalized_relative_path
	) as Node3D
	if cached_prototype != null:
		return cached_prototype
	if bool(prototype_cache_state.call(
		"is_animation_static",
		normalized_relative_path
	)):
		return null

	var animated_model_data: Dictionary = raw_model_repository.call(
		"load_animated_model_data",
		extracted_directory,
		normalized_relative_path
	)
	var animated_surfaces: Array = animated_model_data.get(
		"animated_surfaces",
		[]
	)
	if animated_model_data.is_empty() or animated_surfaces.is_empty():
		prototype_cache_state.call(
			"mark_animation_static",
			normalized_relative_path
		)
		return null

	var prototype := build_animated_prototype(
		animated_model_data,
		extracted_directory
	)
	if prototype == null or prototype.get_child_count() <= 0:
		prototype_cache_state.call(
			"mark_animation_static",
			normalized_relative_path
		)
		return null

	prototype = prototype_cache_state.call(
		"adopt_animated_prototype",
		normalized_relative_path,
		prototype
	) as Node3D
	if debug_logging_enabled:
		print(
			(
				"M2_NATIVE_ANIM_CACHE path=%s surfaces=%d bones=%d "
				+ "anim_id=%d seq=%d score=%d length=%.2f"
			)
			% [
				normalized_relative_path,
				animated_surfaces.size(),
				(animated_model_data.get("bones", []) as Array).size(),
				int(animated_model_data.get("animation_id", -1)),
				int(animated_model_data.get("animation_sequence_index", -1)),
				int(animated_model_data.get("animation_activity_score", 0)),
				float(animated_model_data.get("animation_length", 0.0)),
			]
		)
	return prototype


## Builds one prototype through the exact native animated builder dependency.
func build_animated_prototype(
	animated_model_data: Dictionary,
	extracted_directory: String
) -> Node3D:
	if _native_animated_builder == null:
		return null
	return _native_animated_builder.call(
		"build",
		animated_model_data,
		extracted_directory
	) as Node3D
