extends Node3D
class_name TrackGenerator

## Generatore procedurale di binari ferroviari

# Configurazione binari
@export var track_width: float = 2.0
@export var rail_height: float = 0.15
@export var rail_width: float = 0.1
@export var sleeper_spacing: float = 1.0
@export var sleeper_width: float = 2.4
@export var sleeper_height: float = 0.1
@export var sleeper_depth: float = 0.25

# Configurazione generazione
@export var segment_length: float = 20.0
@export var curve_points: int = 24
@export var min_curve_radius: float = 50.0
@export var max_curve_radius: float = 80.0
@export var max_curve_angle: float = 30.0
@export var straight_probability: float = 0.5
@export var segments_ahead: int = 12
@export var segments_behind: int = 4

# Materiali
var rail_material: StandardMaterial3D
var sleeper_material: StandardMaterial3D
var ballast_material: StandardMaterial3D

# Stato del tracciato
var segments: Array = []
var total_length: float = 0.0

# Posizione corrente della "testa" del binario
var head_position: Vector3 = Vector3.ZERO
var head_direction: Vector3 = Vector3(0, 0, -1)  # Avanti in Godot

# Per curve più naturali
var last_curve_dir: int = 0


func _ready() -> void:
	_setup_materials()
	_generate_initial_track()


func _setup_materials() -> void:
	rail_material = StandardMaterial3D.new()
	rail_material.albedo_color = Color(0.35, 0.35, 0.4)
	rail_material.metallic = 0.8
	rail_material.roughness = 0.3
	
	sleeper_material = StandardMaterial3D.new()
	sleeper_material.albedo_color = Color(0.4, 0.28, 0.18)
	sleeper_material.roughness = 0.9
	
	ballast_material = StandardMaterial3D.new()
	ballast_material.albedo_color = Color(0.55, 0.5, 0.45)
	ballast_material.roughness = 1.0


func _generate_initial_track() -> void:
	for i in range(segments_ahead):
		_add_next_segment()


func _add_next_segment() -> void:
	var is_curve = randf() > straight_probability
	
	var segment: Dictionary
	if is_curve:
		segment = _create_curve_segment()
	else:
		segment = _create_straight_segment()
	
	# Crea la mesh visiva
	_create_segment_mesh(segment)
	
	# Aggiorna la testa del binario per il prossimo segmento
	head_position = segment["end_position"]
	head_direction = segment["end_direction"]
	total_length += segment["length"]
	
	segments.append(segment)


func _create_straight_segment() -> Dictionary:
	var length = segment_length
	var end_pos = head_position + head_direction * length
	
	# Genera punti lungo il segmento dritto
	var points: Array = []
	var tangents: Array = []
	var num_points = 10
	
	for i in range(num_points + 1):
		var t = float(i) / float(num_points)
		var pos = head_position.lerp(end_pos, t)
		points.append(pos)
		tangents.append(head_direction)
	
	return {
		"start_position": head_position,
		"end_position": end_pos,
		"start_direction": head_direction,
		"end_direction": head_direction,
		"length": length,
		"start_distance": total_length,
		"is_curve": false,
		"path_points": points,
		"path_tangents": tangents,
		"mesh_instance": null
	}


func _create_curve_segment() -> Dictionary:
	var radius = randf_range(min_curve_radius, max_curve_radius)
	var angle_deg = randf_range(15.0, max_curve_angle)
	
	# Alterna direzione curve per percorso naturale
	var curve_dir: int
	if last_curve_dir == 0:
		curve_dir = 1 if randf() > 0.5 else -1
	else:
		curve_dir = -last_curve_dir if randf() > 0.3 else last_curve_dir
	last_curve_dir = curve_dir
	
	# Calcola il centro della curva
	var right = head_direction.cross(Vector3.UP).normalized()
	var center = head_position + right * radius * curve_dir
	
	# Lunghezza dell'arco
	var angle_rad = deg_to_rad(angle_deg)
	var arc_length = radius * angle_rad
	
	# Genera punti lungo la curva
	var points: Array = []
	var tangents: Array = []
	
	for i in range(curve_points + 1):
		var t = float(i) / float(curve_points)
		var current_angle = angle_rad * t * curve_dir
		
		# Posizione: ruota il vettore dal centro al punto iniziale
		var start_to_center = head_position - center
		var rotated_vec = _rotate_y(start_to_center, current_angle)
		var pos = center + rotated_vec
		
		# Tangente: ruota la direzione iniziale
		var tangent = _rotate_y(head_direction, current_angle)
		
		points.append(pos)
		tangents.append(tangent)
	
	var end_pos = points[points.size() - 1]
	var end_dir = tangents[tangents.size() - 1]
	
	return {
		"start_position": head_position,
		"end_position": end_pos,
		"start_direction": head_direction,
		"end_direction": end_dir,
		"length": arc_length,
		"start_distance": total_length,
		"is_curve": true,
		"path_points": points,
		"path_tangents": tangents,
		"mesh_instance": null
	}


func _rotate_y(vec: Vector3, angle: float) -> Vector3:
	var cos_a = cos(angle)
	var sin_a = sin(angle)
	return Vector3(
		vec.x * cos_a - vec.z * sin_a,
		vec.y,
		vec.x * sin_a + vec.z * cos_a
	)


func _create_segment_mesh(segment: Dictionary) -> void:
	var mesh_instance = MeshInstance3D.new()
	var array_mesh = ArrayMesh.new()
	
	var rail_arrays = _generate_rails_mesh(segment)
	if rail_arrays[Mesh.ARRAY_VERTEX].size() > 0:
		array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, rail_arrays)
		array_mesh.surface_set_material(array_mesh.get_surface_count() - 1, rail_material)
	
	var sleeper_arrays = _generate_sleepers_mesh(segment)
	if sleeper_arrays[Mesh.ARRAY_VERTEX].size() > 0:
		array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, sleeper_arrays)
		array_mesh.surface_set_material(array_mesh.get_surface_count() - 1, sleeper_material)
	
	var ballast_arrays = _generate_ballast_mesh(segment)
	if ballast_arrays[Mesh.ARRAY_VERTEX].size() > 0:
		array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, ballast_arrays)
		array_mesh.surface_set_material(array_mesh.get_surface_count() - 1, ballast_material)
	
	mesh_instance.mesh = array_mesh
	mesh_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
	add_child(mesh_instance)
	segment["mesh_instance"] = mesh_instance


func _generate_rails_mesh(segment: Dictionary) -> Array:
	var vertices = PackedVector3Array()
	var normals = PackedVector3Array()
	var uvs = PackedVector2Array()
	var indices = PackedInt32Array()
	
	var half_track = track_width / 2.0
	var half_rail = rail_width / 2.0
	var path_points: Array = segment["path_points"]
	var path_tangents: Array = segment["path_tangents"]
	
	for rail_side in [-1, 1]:
		var rail_offset = half_track * rail_side
		var vertex_offset = vertices.size()
		
		for i in range(path_points.size()):
			var pos: Vector3 = path_points[i]
			var tangent: Vector3 = path_tangents[i]
			var right = tangent.cross(Vector3.UP).normalized()
			
			var base = pos + right * rail_offset
			
			# 4 vertici per sezione
			vertices.append(base - right * half_rail)
			vertices.append(base + right * half_rail)
			vertices.append(base + right * half_rail + Vector3.UP * rail_height)
			vertices.append(base - right * half_rail + Vector3.UP * rail_height)
			
			for _j in range(4):
				normals.append(Vector3.UP)
			
			var u = float(i) / float(path_points.size())
			uvs.append(Vector2(0, u))
			uvs.append(Vector2(1, u))
			uvs.append(Vector2(1, u))
			uvs.append(Vector2(0, u))
		
		# Collega le sezioni
		for i in range(path_points.size() - 1):
			var base_idx = vertex_offset + i * 4
			
			# Top
			indices.append(base_idx + 2)
			indices.append(base_idx + 3)
			indices.append(base_idx + 7)
			indices.append(base_idx + 2)
			indices.append(base_idx + 7)
			indices.append(base_idx + 6)
			
			# Sides
			indices.append(base_idx + 0)
			indices.append(base_idx + 4)
			indices.append(base_idx + 7)
			indices.append(base_idx + 0)
			indices.append(base_idx + 7)
			indices.append(base_idx + 3)
			
			indices.append(base_idx + 1)
			indices.append(base_idx + 2)
			indices.append(base_idx + 6)
			indices.append(base_idx + 1)
			indices.append(base_idx + 6)
			indices.append(base_idx + 5)
	
	var arrays = []
	arrays.resize(Mesh.ARRAY_MAX)
	arrays[Mesh.ARRAY_VERTEX] = vertices
	arrays[Mesh.ARRAY_NORMAL] = normals
	arrays[Mesh.ARRAY_TEX_UV] = uvs
	arrays[Mesh.ARRAY_INDEX] = indices
	return arrays


func _generate_sleepers_mesh(segment: Dictionary) -> Array:
	var vertices = PackedVector3Array()
	var normals = PackedVector3Array()
	var uvs = PackedVector2Array()
	var indices = PackedInt32Array()
	
	var seg_length: float = segment["length"]
	var num_sleepers = max(1, int(seg_length / sleeper_spacing))
	var path_points: Array = segment["path_points"]
	var path_tangents: Array = segment["path_tangents"]
	
	for i in range(num_sleepers):
		var t = float(i) / float(num_sleepers)
		var pos = _interpolate_path(path_points, t)
		var tangent = _interpolate_path(path_tangents, t).normalized()
		var right = tangent.cross(Vector3.UP).normalized()
		
		var half_w = sleeper_width / 2.0
		var half_d = sleeper_depth / 2.0
		var base_idx = vertices.size()
		
		var corners = [
			pos + right * (-half_w) + tangent * (-half_d),
			pos + right * half_w + tangent * (-half_d),
			pos + right * half_w + tangent * half_d,
			pos + right * (-half_w) + tangent * half_d,
		]
		
		# Bottom vertices
		for c in corners:
			vertices.append(c)
			normals.append(Vector3.DOWN)
			uvs.append(Vector2(0, 0))
		
		# Top vertices
		for c in corners:
			vertices.append(c + Vector3.UP * sleeper_height)
			normals.append(Vector3.UP)
			uvs.append(Vector2(1, 1))
		
		# Top face
		indices.append(base_idx + 4)
		indices.append(base_idx + 5)
		indices.append(base_idx + 6)
		indices.append(base_idx + 4)
		indices.append(base_idx + 6)
		indices.append(base_idx + 7)
		
		# Side faces
		for j in range(4):
			var j_next = (j + 1) % 4
			indices.append(base_idx + j)
			indices.append(base_idx + j_next)
			indices.append(base_idx + j_next + 4)
			indices.append(base_idx + j)
			indices.append(base_idx + j_next + 4)
			indices.append(base_idx + j + 4)
	
	var arrays = []
	arrays.resize(Mesh.ARRAY_MAX)
	arrays[Mesh.ARRAY_VERTEX] = vertices
	arrays[Mesh.ARRAY_NORMAL] = normals
	arrays[Mesh.ARRAY_TEX_UV] = uvs
	arrays[Mesh.ARRAY_INDEX] = indices
	return arrays


func _generate_ballast_mesh(segment: Dictionary) -> Array:
	var vertices = PackedVector3Array()
	var normals = PackedVector3Array()
	var uvs = PackedVector2Array()
	var indices = PackedInt32Array()
	
	var ballast_width = sleeper_width * 1.3
	var ballast_y = -0.08
	var path_points: Array = segment["path_points"]
	var path_tangents: Array = segment["path_tangents"]
	
	for i in range(path_points.size()):
		var pos: Vector3 = path_points[i]
		var tangent: Vector3 = path_tangents[i]
		var right = tangent.cross(Vector3.UP).normalized()
		var half_w = ballast_width / 2.0
		
		vertices.append(pos + right * (-half_w) + Vector3.UP * ballast_y)
		vertices.append(pos + right * half_w + Vector3.UP * ballast_y)
		normals.append(Vector3.UP)
		normals.append(Vector3.UP)
		
		var u = float(i) / float(path_points.size())
		uvs.append(Vector2(0, u))
		uvs.append(Vector2(1, u))
	
	for i in range(path_points.size() - 1):
		var base_idx = i * 2
		indices.append(base_idx)
		indices.append(base_idx + 1)
		indices.append(base_idx + 3)
		indices.append(base_idx)
		indices.append(base_idx + 3)
		indices.append(base_idx + 2)
	
	var arrays = []
	arrays.resize(Mesh.ARRAY_MAX)
	arrays[Mesh.ARRAY_VERTEX] = vertices
	arrays[Mesh.ARRAY_NORMAL] = normals
	arrays[Mesh.ARRAY_TEX_UV] = uvs
	arrays[Mesh.ARRAY_INDEX] = indices
	return arrays


func _interpolate_path(points: Array, t: float) -> Vector3:
	if points.size() < 2:
		return points[0] if points.size() > 0 else Vector3.ZERO
	
	t = clampf(t, 0.0, 1.0)
	var idx = t * (points.size() - 1)
	var i = int(idx)
	var frac = idx - i
	
	if i >= points.size() - 1:
		return points[points.size() - 1]
	
	var p1: Vector3 = points[i]
	var p2: Vector3 = points[i + 1]
	return p1.lerp(p2, frac)


func update_track(train_distance: float) -> void:
	# Rimuovi segmenti vecchi
	while segments.size() > 0:
		var first: Dictionary = segments[0]
		var seg_end = first["start_distance"] + first["length"]
		
		if train_distance - seg_end > segment_length * segments_behind:
			_remove_segment(first)
		else:
			break
	
	# Aggiungi nuovi segmenti
	while total_length - train_distance < segment_length * segments_ahead:
		_add_next_segment()


func _remove_segment(segment: Dictionary) -> void:
	var mesh: MeshInstance3D = segment["mesh_instance"]
	if mesh:
		mesh.queue_free()
	segments.erase(segment)


func get_position_on_track(distance: float) -> Dictionary:
	for segment in segments:
		var seg_start: float = segment["start_distance"]
		var seg_end = seg_start + segment["length"]
		
		if distance >= seg_start and distance <= seg_end:
			var seg_len: float = segment["length"]
			var t = (distance - seg_start) / seg_len if seg_len > 0 else 0.0
			
			var path_points: Array = segment["path_points"]
			var path_tangents: Array = segment["path_tangents"]
			
			return {
				"position": _interpolate_path(path_points, t),
				"direction": _interpolate_path(path_tangents, t).normalized(),
				"valid": true
			}
	
	# Se oltre tutti i segmenti, usa l'ultimo punto
	if segments.size() > 0:
		var last: Dictionary = segments[segments.size() - 1]
		return {
			"position": last["end_position"],
			"direction": last["end_direction"],
			"valid": true
		}
	
	return {
		"position": Vector3.ZERO,
		"direction": Vector3(0, 0, -1),
		"valid": false
	}


func get_total_length() -> float:
	return total_length
