extends SceneTree

# Собирает нативного персонажа (Skeleton2D + взвешенные Polygon2D + AnimationPlayer с
# запечёнными анимациями) и СОХРАНЯЕТ как редактируемую сцену res://characters/elf1/elf1_native.tscn.
# Запуск: godot ... --headless --script res://tools/build_tscn.gd

const GEOM := "res://tools/rig/elf1_combat.json"
const ANIMS := "res://tools/rig/elf1_combat_anims.json"
const PAGE := "res://characters/elf1/spine/elf1_combat.png"
const OUT := "res://characters/elf1/elf1_native.tscn"
const VP := Vector2(1280, 720)

var _root: Node2D
var _skel: Skeleton2D
var _bones := []
var _by_name := {}
var _poly_by_slot := {}      # имя слота -> Polygon2D
var _base_rank := {}         # имя слота -> setup-ранг (индекс в порядке отрисовки)

func _initialize():
	_build_rig()
	_build_anims()
	_set_owner(_root, _root)

	var packed := PackedScene.new()
	var err := packed.pack(_root)
	if err != OK:
		push_error("pack failed: %d" % err); quit(); return
	err = ResourceSaver.save(packed, OUT)
	print("SAVE ", OUT, " -> ", err, "  (кости=%d, привязок=%d)" % [_bones.size(), _skel.get_child_count() - _bones_direct])
	quit()

var _bones_direct := 0

func _build_rig():
	var f := FileAccess.open(GEOM, FileAccess.READ)
	var data: Dictionary = JSON.parse_string(f.get_as_text()); f.close()
	var tex: Texture2D = load(PAGE)
	var ts := tex.get_size()

	_root = Node2D.new(); _root.name = "ElfNative"
	var spine := Node2D.new(); spine.name = "SpineRoot"; _root.add_child(spine)
	_skel = Skeleton2D.new(); _skel.name = "Skeleton2D"; spine.add_child(_skel)

	for bd in data["bones"]:
		var b := Bone2D.new()
		b.name = bd["name"]
		b.set_autocalculate_length_and_angle(false)
		b.set_length(float(bd["length"]))
		b.set_bone_angle(0.0)
		var t := Transform2D(deg_to_rad(float(bd["rotation"])), Vector2(bd["scaleX"], bd["scaleY"]), 0.0, Vector2(bd["x"], bd["y"]))
		var parent = _by_name.get(bd["parent"], _skel)
		parent.add_child(b)
		b.transform = t; b.rest = t
		_bones.append(b); _by_name[bd["name"]] = b
	_bones_direct = _skel.get_child_count()

	var minv := Vector2(1e9, 1e9); var maxv := Vector2(-1e9, -1e9)
	var att_rank := 0
	for att in data["attachments"]:
		var world: Array = att["world"]; var uv: Array = att["uv"]
		var tris: Array = att["triangles"]; var weights: Array = att["weights"]
		var vc := world.size() / 2
		var poly := Polygon2D.new(); poly.name = String(att["slot"]); poly.texture = tex
		var pts := PackedVector2Array(); var uvs := PackedVector2Array()
		pts.resize(vc); uvs.resize(vc)
		for i in vc:
			var p := Vector2(world[i * 2], world[i * 2 + 1]); pts[i] = p
			uvs[i] = Vector2(uv[i * 2] * ts.x, uv[i * 2 + 1] * ts.y)
			minv = minv.min(p); maxv = maxv.max(p)
		poly.polygon = pts; poly.uv = uvs
		var polys := []
		for t in range(0, tris.size(), 3):
			polys.append(PackedInt32Array([tris[t], tris[t + 1], tris[t + 2]]))
		poly.polygons = polys
		var c: Array = att["color"]; poly.modulate = Color(c[0], c[1], c[2], c[3])
		poly.z_index = att_rank                    # базовый порядок отрисовки (setup)
		_skel.add_child(poly)
		poly.skeleton = poly.get_path_to(_skel)
		_poly_by_slot[String(att["slot"])] = poly
		_base_rank[String(att["slot"])] = att_rank
		att_rank += 1
		var bw := {}
		for i in vc:
			for pair in weights[i]:
				var bi := int(pair[0])
				if not bw.has(bi): var a := PackedFloat32Array(); a.resize(vc); bw[bi] = a
				bw[bi][i] = float(pair[1])
		for bi in bw:
			poly.add_bone(_skel.get_path_to(_bones[bi]), bw[bi])

	var size := maxv - minv; var center := (minv + maxv) * 0.5
	var fit: float = min(VP.x * 0.55 / size.x, VP.y * 0.8 / size.y)
	spine.scale = Vector2(fit, -fit)
	spine.position = Vector2(VP.x * 0.5 - center.x * fit, VP.y * 0.5 + center.y * fit)

func _build_anims():
	var f := FileAccess.open(ANIMS, FileAccess.READ)
	var data: Dictionary = JSON.parse_string(f.get_as_text()); f.close()
	var fps := float(data["fps"])
	var bone_names: Array = data["bones"]
	var slot_names: Array = data.get("slots", [])
	var anims: Dictionary = data["animations"]

	var player := AnimationPlayer.new(); player.name = "AnimationPlayer"
	_root.add_child(player)
	player.root_node = player.get_path_to(_skel)
	var lib := AnimationLibrary.new()

	for anim_name in anims:
		var a: Dictionary = anims[anim_name]
		var frames: Array = a["frames"]; var nf := frames.size()
		var anim := Animation.new(); anim.length = float(a["duration"]); anim.loop_mode = Animation.LOOP_LINEAR
		for bi in bone_names.size():
			var bn: String = bone_names[bi]
			if not _by_name.has(bn): continue
			var rel := String(_skel.get_path_to(_by_name[bn]))
			var f0: Array = frames[0][bi]
			var pv := false; var rv := false; var sv := false
			for fr in range(1, nf):
				var v: Array = frames[fr][bi]
				if not pv and (not is_equal_approx(v[0], f0[0]) or not is_equal_approx(v[1], f0[1])): pv = true
				if not rv and not is_equal_approx(v[2], f0[2]): rv = true
				if not sv and (not is_equal_approx(v[3], f0[3]) or not is_equal_approx(v[4], f0[4])): sv = true
			_add_track(anim, rel + ":position", frames, bi, fps, nf, 0, pv)
			_add_track(anim, rel + ":rotation", frames, bi, fps, nf, 1, rv)
			_add_track(anim, rel + ":scale", frames, bi, fps, nf, 2, sv)

		# --- draw order -> z_index (дискретно) ---
		var draworder: Array = a.get("draworder", [])
		if not draworder.is_empty() and not slot_names.is_empty():
			# ранг полигон-слота = позиция среди слотов-С-привязкой в порядке отрисовки
			var ranks := {}   # имя слота -> [ранг по кадрам]
			for name in _poly_by_slot: ranks[name] = []
			for fr in nf:
				var r := 0
				for si in draworder[fr]:
					var sn: String = slot_names[si]
					if _poly_by_slot.has(sn):
						ranks[sn].append(r); r += 1
			for sn in ranks:
				var seq: Array = ranks[sn]
				var vary := false
				for v in seq:
					if v != _base_rank[sn]: vary = true; break
				if not vary: continue
				var rel := String(_skel.get_path_to(_poly_by_slot[sn]))
				var ti := anim.add_track(Animation.TYPE_VALUE)
				anim.track_set_path(ti, rel + ":z_index")
				anim.value_track_set_update_mode(ti, Animation.UPDATE_DISCRETE)
				anim.track_set_interpolation_type(ti, Animation.INTERPOLATION_NEAREST)
				for fr in nf:
					anim.track_insert_key(ti, fr / fps, seq[fr])

		lib.add_animation(anim_name, anim)
	player.add_animation_library("", lib)

func _add_track(anim: Animation, path: String, frames: Array, bi: int, fps: float, nf: int, kind: int, vary: bool):
	if not vary: return
	var ti := anim.add_track(Animation.TYPE_VALUE)
	anim.track_set_path(ti, path)
	anim.track_set_interpolation_type(ti, Animation.INTERPOLATION_LINEAR)
	for fr in nf:
		var v: Array = frames[fr][bi]
		var val
		match kind:
			0: val = Vector2(v[0], v[1])
			1: val = deg_to_rad(float(v[2]))
			2: val = Vector2(v[3], v[4])
		anim.track_insert_key(ti, fr / fps, val)

func _set_owner(node: Node, owner: Node):
	for ch in node.get_children():
		ch.owner = owner
		_set_owner(ch, owner)
