## Builds a runtime-animated M2 node from native M2Loader data.
## M2 skinning is evaluated explicitly because WoW's pivot/palette model does
## not map cleanly to Godot Skeleton3D/Skin for composite doodads.
class_name M2NativeAnimatedBuilder

const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
const M2_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.gd")


static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
	var root := Node3D.new()
	root.name = "M2NativeAnimated"

	var bones: Array = data.get("bones", [])
	var surfaces: Array = data.get("animated_surfaces", [])
	if bones.is_empty() or surfaces.is_empty():
		return root

	var mesh := _build_mesh(data, surfaces, extracted_dir)
	if mesh == null or mesh.get_surface_count() <= 0:
		return root

	var mesh_instance := MeshInstance3D.new()
	mesh_instance.name = "Mesh"
	mesh_instance.mesh = mesh
	root.add_child(mesh_instance)

	var animator = M2_NATIVE_ANIMATOR_SCRIPT.new()
	animator.name = "M2NativeAnimator"
	root.add_child(animator)
	animator.setup(mesh_instance, bones, surfaces, float(data.get("animation_length", 0.0)))
	return root


static func _build_mesh(data: Dictionary, surfaces: Array, extracted_dir: String) -> ArrayMesh:
	var mesh := ArrayMesh.new()
	var textures: PackedStringArray = data.get("textures", PackedStringArray())
	var texture_types: PackedInt32Array = data.get("texture_types", PackedInt32Array())
	var texture_flags: PackedInt32Array = data.get("texture_flags", PackedInt32Array())
	var materials: Array = data.get("materials", [])
	var tex_combos: PackedInt32Array = data.get("texture_combos", PackedInt32Array())
	var tex_coord_combos: PackedInt32Array = data.get("texture_coord_combos", PackedInt32Array())
	var tex_weight_combos: PackedInt32Array = data.get("texture_weight_combos", PackedInt32Array())
	var tex_transform_combos: PackedInt32Array = data.get("texture_transform_combos", PackedInt32Array())
	var tex_combiner_combos: PackedInt32Array = data.get("texture_combiner_combos", PackedInt32Array())
	var m2_colors: Array = data.get("m2_colors", [])
	var tex_weights: PackedFloat32Array = data.get("texture_weights", PackedFloat32Array())
	var tex_transforms: Array = data.get("texture_transforms", [])
	var m2_flags: int = int(data.get("m2_flags", 0))
	var model_path := String(data.get("model_path", ""))

	for surface_variant in surfaces:
		if not (surface_variant is Dictionary):
			continue
		var surface: Dictionary = surface_variant
		var verts: PackedVector3Array = surface.get("vertices", PackedVector3Array())
		var indices: PackedInt32Array = surface.get("indices", PackedInt32Array())
		if verts.is_empty() or indices.is_empty():
			continue

		var arrays := []
		arrays.resize(Mesh.ARRAY_MAX)
		arrays[Mesh.ARRAY_VERTEX] = verts
		var normals: PackedVector3Array = surface.get("normals", PackedVector3Array())
		if normals.size() == verts.size():
			arrays[Mesh.ARRAY_NORMAL] = normals
		var uvs: PackedVector2Array = surface.get("uvs", PackedVector2Array())
		if uvs.size() == verts.size():
			arrays[Mesh.ARRAY_TEX_UV] = uvs
		var uvs2: PackedVector2Array = surface.get("uvs2", PackedVector2Array())
		if uvs2.size() == verts.size():
			arrays[Mesh.ARRAY_TEX_UV2] = uvs2
		arrays[Mesh.ARRAY_INDEX] = indices

		var surface_flags := M2_BUILDER_SCRIPT._apply_billboard_custom_data(arrays, surface, verts.size())
		mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays, [], {}, surface_flags)
		var surface_index := mesh.get_surface_count() - 1
		var material_id := int(surface.get("material_id", -1))
		var texture_combo_index := int(surface.get("texture_combo_index", -1))
		var mat_def: Dictionary = materials[material_id] if material_id >= 0 and material_id < materials.size() else {}
		mesh.surface_set_material(
			surface_index,
			M2_BUILDER_SCRIPT._build_material(
				mat_def,
				texture_combo_index,
				textures,
				texture_types,
				texture_flags,
				tex_combos,
				tex_coord_combos,
				tex_weight_combos,
				tex_transform_combos,
				tex_combiner_combos,
				m2_colors,
				tex_weights,
				tex_transforms,
				m2_flags,
				extracted_dir,
				model_path,
				surface))

	return mesh
