class_name M2RuntimeMeshFinalizer
extends RefCounted

## Finalizes stale cached M2 Meshes using caller-supplied raw data. The caller
## owns raw-file I/O, cache adoption, missing state and materialization.

const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
const M2_RUNTIME_MESH_REBUILD_CLASSIFIER_SCRIPT := preload(
	"res://src/render/m2/m2_runtime_mesh_rebuild_classifier.gd"
)
const M2_MESH_RESOURCE_EXTRACTOR_SCRIPT := preload(
	"res://src/render/m2/m2_mesh_resource_extractor.gd"
)

const MATERIAL_REFRESH_VERSION := 2
const MATERIAL_REFRESH_VERSION_META := "wow_m2_material_refresh_version"

var _runtime_mesh_rebuild_classifier := M2_RUNTIME_MESH_REBUILD_CLASSIFIER_SCRIPT.new()
var _mesh_resource_extractor := M2_MESH_RESOURCE_EXTRACTOR_SCRIPT.new()


## Returns whether one Mesh is stale and the caller should load raw M2 data.
func requires_raw_data_for_refresh(mesh: Mesh) -> bool:
	return (
		mesh != null
		and int(mesh.get_meta(MATERIAL_REFRESH_VERSION_META, 0)) < MATERIAL_REFRESH_VERSION
	)


## Marks, rebuilds or falls back one stale Mesh using already-loaded raw M2
## data. Current Meshes return unchanged and never inspect raw data.
func finalize_mesh(
		normalized_relative_path: String,
		mesh: Mesh,
		raw_m2_data: Dictionary,
		extracted_directory: String) -> Mesh:
	if mesh == null:
		return null
	if not requires_raw_data_for_refresh(mesh):
		return mesh
	if raw_m2_data.is_empty() or not _runtime_mesh_rebuild_classifier.needs_runtime_mesh_rebuild(
			normalized_relative_path,
			raw_m2_data):
		_mark_mesh_current(mesh)
		return mesh
	var rebuilt_mesh := _rebuild_mesh(raw_m2_data, extracted_directory)
	if rebuilt_mesh != null:
		return rebuilt_mesh
	_mark_mesh_current(mesh)
	return mesh


## Clears memoized per-path rebuild decisions at the existing map/shutdown sites.
func clear() -> void:
	_runtime_mesh_rebuild_classifier.clear()


## Returns the memoized rebuild-decision count for diagnostics and verification.
func cached_rebuild_decision_count() -> int:
	return _runtime_mesh_rebuild_classifier.cached_path_count()


func _mark_mesh_current(mesh: Mesh) -> void:
	mesh.set_meta(MATERIAL_REFRESH_VERSION_META, MATERIAL_REFRESH_VERSION)


func _rebuild_mesh(raw_m2_data: Dictionary, extracted_directory: String) -> Mesh:
	var temporary_prototype: Node3D = M2_BUILDER_SCRIPT.build(
		raw_m2_data,
		extracted_directory
	)
	if temporary_prototype == null:
		return null
	var rebuilt_mesh := _mesh_resource_extractor.find_first_mesh_in_subtree(
		temporary_prototype
	)
	temporary_prototype.free()
	return rebuilt_mesh
