extends Path3D

@export_group("Train")
@export var train_speed: float = 6.0
@export var is_inmotion: bool = true
@export var train_model: PackedScene 
@export var axes_distance: float = 3.0 
@export var rail_distance: float = 1.2 
@export var cameras: Node3D 
@export_range(0.0, 1.0) var basic_swing: float = 0.5 

@export_group("Rails Bulding")
@export var sleepers_distance: float = 1.0
@export var sleepers_model: PackedScene 

@export_group("Manual Controls")
@export var speed_max: float = 15.0
@export var speed_min: float = -5.0 
@export var manual_acceleration: float = 5.0 

@export_group("Train Stops")
@export var enable_stops: bool = true
@export var stop_time: float = 4.0 
@export var brake_distance: float = 15.0 
@export var restart_time: float = 3.0 
@export var fireworks_scene: PackedScene 

var fireworks_colors: Array[Color] = [
	Color(1.0, 0.2, 0.2), # Rosso vivo
	Color(0.2, 1.0, 0.2), # Verde lime
	Color(0.3, 0.5, 1.0), # Blu cielo
	Color(1.0, 0.8, 0.1), # Oro
	Color(0.8, 0.2, 1.0), # Viola
	Color(0.1, 1.0, 0.9)  # Ciano
]

var train_instance: Node3D
var train_progress: float = 0.0
var swing_time: float = 0.0

var stop_offset: Array[float] = []
var stops_position: Array[Vector3] = [] 
var stop_ongoing: bool = false
var next_stop_index: int = 0
var stop_multiply: float = 1.0
var is_restarting: bool = false
var tween_restart: Tween

func _ready() -> void:
	if curve != null and curve.get_baked_length() > 0:
		build_rails()
		build_train()
		_plan_stops()
	else:
		print("WARNING: Draw your Path3D!")

func build_rails() -> void:
	var mat_wood = StandardMaterial3D.new()
	mat_wood.albedo_color = Color(0.35, 0.2, 0.1) 
	var mat_iron = StandardMaterial3D.new()
	mat_iron.albedo_color = Color(0.6, 0.6, 0.65) 
	mat_iron.metallic = 0.8
	
	var mesh_sleeper = BoxMesh.new()
	mesh_sleeper.size = Vector3(rail_distance + 0.6, 0.1, 0.3)
	var mesh_rail = BoxMesh.new()
	mesh_rail.size = Vector3(0.1, 0.15, sleepers_distance + 0.05) 
	
	var total_length = curve.get_baked_length()
	var piece_number = int(total_length / sleepers_distance)
	
	for i in range(piece_number):
		var distance = i * sleepers_distance
		var rail_piece = Node3D.new()
		add_child(rail_piece)
		
		var local_pos = curve.sample_baked(distance, true)
		var distance_forward = distance + 0.1
		var local_pos_forward = Vector3.ZERO
		
		if distance_forward > total_length:
			var pos_back = curve.sample_baked(distance - 0.1, true)
			local_pos_forward = local_pos + (local_pos - pos_back)
		else:
			local_pos_forward = curve.sample_baked(distance_forward, true)
			
		rail_piece.position = local_pos
		var global_pos = to_global(local_pos)
		var global_pos_forward = to_global(local_pos_forward)
		if global_pos.distance_to(global_pos_forward) > 0.001:
			rail_piece.look_at(global_pos_forward, Vector3.UP)
		
		if sleepers_model != null:
			var sleeper_custom = sleepers_model.instantiate()
			rail_piece.add_child(sleeper_custom)
		else:
			var sleeper = MeshInstance3D.new()
			sleeper.mesh = mesh_sleeper
			sleeper.material_override = mat_wood
			rail_piece.add_child(sleeper)
		
		var rail_sx = MeshInstance3D.new()
		rail_sx.mesh = mesh_rail
		rail_sx.material_override = mat_iron
		rail_sx.position = Vector3(-rail_distance / 2.0, 0.1, 0) 
		rail_piece.add_child(rail_sx)
		
		var rail_dx = MeshInstance3D.new()
		rail_dx.mesh = mesh_rail
		rail_dx.material_override = mat_iron
		rail_dx.position = Vector3(rail_distance / 2.0, 0.1, 0) 
		rail_piece.add_child(rail_dx)

func _plan_stops() -> void:
	var currnet_stops = []
	for child in get_children():
		if child is Marker3D:
			var offset = curve.get_closest_offset(child.position)
			currnet_stops.append({
				"offset": offset,
				"position": child.global_position 
			})
	currnet_stops.sort_custom(func(a, b): return a["offset"] < b["offset"])
	for data in currnet_stops:
		stop_offset.append(data["offset"])
		stops_position.append(data["position"])

func _input(event: InputEvent) -> void:
	if event is InputEventKey and event.pressed and not event.echo:
		if event.keycode == KEY_T:
			_goto_next_stop()
		elif event.keycode == KEY_F:
			_test_manual_fires()

func _test_manual_fires() -> void:
	if fireworks_scene == null or train_instance == null: return
		
	var firework_number = randi_range(5, 8)
	for i in range(firework_number):
		var fire_root = fireworks_scene.instantiate()
		get_tree().current_scene.add_child(fire_root)
		
		var offset_x = randf_range(-6.0, 6.0)
		var offset_z = randf_range(-6.0, 6.0)
		fire_root.global_position = train_instance.global_position + Vector3(offset_x, 0.5, offset_z)
		
		if fire_root.has_method("set_color"):
			fire_root.set_color(fireworks_colors.pick_random())
		if fire_root.has_method("turnon"):
			fire_root.turnon()
		
		await get_tree().create_timer(randf_range(0.2, 0.6)).timeout

func _goto_next_stop() -> void:
	if stop_offset.size() == 0 or stop_ongoing or is_restarting:
		return
	var target_offset = stop_offset[next_stop_index]
	train_progress = target_offset
	_execute_stop(train_speed >= 0)

func _physics_process(delta: float) -> void:
	input_controls_management(delta) 
	train_move(delta)

func input_controls_management(delta: float) -> void:
	if not is_inmotion: return
	if Input.is_action_pressed("ui_up"):
		train_speed += manual_acceleration * delta
	elif Input.is_action_pressed("ui_down"):
		train_speed -= manual_acceleration * delta
	train_speed = clamp(train_speed, speed_min, speed_max)

func train_move(delta: float) -> void:
	if is_inmotion and train_instance and curve:
		var total_length = curve.get_baked_length()
		if total_length <= 0: return
		
		if not stop_ongoing:
			var last_progress = train_progress
			
			if enable_stops and stop_offset.size() > 0 and not is_restarting:
				var target_offset = stop_offset[next_stop_index]
				var dist = target_offset - train_progress
				dist = wrapf(dist + total_length / 2.0, 0.0, total_length) - total_length / 2.0
				
				if abs(dist) < brake_distance and sign(dist) == sign(train_speed):
					var t = abs(dist) / brake_distance
					stop_multiply = max(smoothstep(0.0, 1.0, t), 0.05)
				else:
					stop_multiply = 1.0
			
			var current_speed = train_speed * stop_multiply
			train_progress += current_speed * delta
			
			if enable_stops and stop_offset.size() > 0:
				var target_offset = stop_offset[next_stop_index]
				var exceeded_forward = (current_speed > 0 and last_progress < target_offset and train_progress >= target_offset)
				var exceeded_back = (current_speed < 0 and last_progress > target_offset and train_progress <= target_offset)
				
				if exceeded_forward or exceeded_back:
					train_progress = target_offset 
					_execute_stop(current_speed > 0)
			
			if train_progress > total_length:
				train_progress -= total_length
				if stop_offset.size() > 0: next_stop_index = 0 
			elif train_progress < 0: 
				train_progress += total_length
				if stop_offset.size() > 0: next_stop_index = stop_offset.size() - 1
		
		var center_position = to_global(curve.sample_baked(train_progress, true))
		var prog_forward = wrapf(train_progress + 2.0, 0.0, total_length)
		var forward_position = to_global(curve.sample_baked(prog_forward, true))
		
		if center_position.distance_to(forward_position) > 0.01:
			train_instance.global_position = center_position
			train_instance.look_at(forward_position, Vector3.UP)
			train_instance.rotate_y(PI) 
			
			if cameras:
				cameras.global_position = center_position
				cameras.global_basis = train_instance.global_basis
				
		if not stop_ongoing:
			var real_speed = abs(train_speed * stop_multiply)
			swing_time += delta * real_speed * 2.0
			var amplitude_z = 0.006 * basic_swing
			var amplitude_x = 0.004 * basic_swing
			train_instance.rotation.z += sin(swing_time) * amplitude_z
			train_instance.rotation.x += cos(swing_time * 0.8) * amplitude_x

func _execute_stop(go_forward: bool = true) -> void:
	stop_ongoing = true
	stop_multiply = 0.0 
	
	var current_stop_index = next_stop_index
	
	if go_forward:
		next_stop_index += 1
		if next_stop_index >= stop_offset.size():
			next_stop_index = 0 
	else:
		next_stop_index -= 1
		if next_stop_index < 0:
			next_stop_index = stop_offset.size() - 1
			
	if fireworks_scene != null:
		var marker_position = stops_position[current_stop_index]
		var firework_number = randi_range(5, 8)
		for i in range(firework_number):
			var fire_root = fireworks_scene.instantiate()
			get_tree().current_scene.add_child(fire_root)
			
			var offset_x = randf_range(-5.0, 5.0)
			var offset_z = randf_range(-5.0, 5.0)
			
			fire_root.global_position = marker_position + Vector3(offset_x, 0.5, offset_z)
			
			if fire_root.has_method("set_color"):
				fire_root.set_color(fireworks_colors.pick_random())
			if fire_root.has_method("turnon"):
				fire_root.turnon() 
				
			await get_tree().create_timer(randf_range(0.3, 0.8)).timeout
		
	await get_tree().create_timer(stop_time).timeout
	
	stop_ongoing = false
	is_restarting = true
	
	if tween_restart and tween_restart.is_valid():
		tween_restart.kill()
		
	tween_restart = create_tween()
	tween_restart.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
	tween_restart.tween_property(self, "stop_multiply", 1.0, restart_time)
	tween_restart.tween_callback(func(): is_restarting = false)

func build_train() -> void:
	if train_model != null:
		train_instance = train_model.instantiate()
		add_child(train_instance)
	else:
		build_default_train()

func build_default_train() -> void:
	train_instance = Node3D.new()
	add_child(train_instance)
	
	var mat_body = StandardMaterial3D.new()
	mat_body.albedo_color = Color(0.8, 0.15, 0.15) 
	var mat_glass = StandardMaterial3D.new()
	mat_glass.albedo_color = Color(0.2, 0.8, 1.0) 
	
	var train_base = MeshInstance3D.new()
	var mesh_base = BoxMesh.new()
	mesh_base.size = Vector3(1.4, 0.8, 3.0)
	train_base.mesh = mesh_base
	train_base.material_override = mat_body
	train_base.position = Vector3(0, 0.6, 0) 
	train_instance.add_child(train_base)
	
	var cabin = MeshInstance3D.new()
	var mesh_cabin = BoxMesh.new()
	mesh_cabin.size = Vector3(1.4, 1.0, 1.2)
	cabin.mesh = mesh_cabin
	cabin.material_override = mat_glass
	cabin.position = Vector3(0, 1.5, -0.8) 
	train_instance.add_child(cabin)
	
	var stack = MeshInstance3D.new()
	var mesh_stack = CylinderMesh.new()
	mesh_stack.top_radius = 0.2
	mesh_stack.bottom_radius = 0.3
	mesh_stack.height = 0.8
	stack.mesh = mesh_stack
	stack.material_override = mat_body
	stack.position = Vector3(0, 1.2, 1.0)
	train_instance.add_child(stack)
