extends Node3D

@export var fish_count: int = 12
@export var school_radius: float = 4.0
@export var swim_speed: float = 2.5

var _fish_meshes: Array[MeshInstance3D] = []
var _fish_velocities: Array[Vector3] = []
var _time: float = 0.0
var _player: CharacterBody3D = null
var _boid_update_timer: float = 0.0

func _ready() -> void:
	_find_player()
	_spawn_fish()


func _find_player() -> void:
	var players := get_tree().get_nodes_in_group("player")
	if players.size() > 0:
		_player = players[0] as CharacterBody3D


func _spawn_fish() -> void:
	var mat := StandardMaterial3D.new()
	mat.albedo_color = Color(0.75, 0.82, 0.95)
	mat.metallic = 0.7
	mat.roughness = 0.3

	for i: int in range(fish_count):
		var mesh_inst := MeshInstance3D.new()
		var cyl := CylinderMesh.new()
		cyl.top_radius = 0.05
		cyl.bottom_radius = 0.15
		cyl.height = 0.4
		mesh_inst.mesh = cyl
		mesh_inst.material_override = mat

		# Random position in sphere
		var offset := Vector3(
			randf_range(-school_radius, school_radius),
			randf_range(-school_radius * 0.5, school_radius * 0.5),
			randf_range(-school_radius, school_radius)
		)
		mesh_inst.position = offset
		# Rotate mesh so cone points forward (cylinder is vertical by default)
		mesh_inst.rotation.z = PI * 0.5

		add_child(mesh_inst)
		_fish_meshes.append(mesh_inst)

		var rand_vel := Vector3(
			randf_range(-1.0, 1.0),
			randf_range(-0.3, 0.3),
			randf_range(-1.0, 1.0)
		).normalized() * swim_speed
		_fish_velocities.append(rand_vel)


func _process(delta: float) -> void:
	_time += delta

	if _player == null:
		_find_player()

	# Throttle boid calculations to 10 Hz
	_boid_update_timer += delta
	if _boid_update_timer >= 0.1:
		_update_boids(_boid_update_timer)
		_boid_update_timer = 0.0

	# Apply velocities every frame for smooth movement
	_apply_velocities(delta)


func _update_boids(dt: float) -> void:
	for i: int in range(_fish_meshes.size()):
		var fish: MeshInstance3D = _fish_meshes[i]
		var vel: Vector3 = _fish_velocities[i]

		# --- Boids: cohesion + alignment with 3 nearest neighbors ---
		var avg_pos: Vector3 = Vector3.ZERO
		var avg_vel: Vector3 = Vector3.ZERO
		var neighbor_count: int = 0

		# Sort by distance to find 3 nearest
		var distances: Array = []
		for j: int in range(_fish_meshes.size()):
			if j == i:
				continue
			var d: float = fish.position.distance_to(_fish_meshes[j].position)
			distances.append({"idx": j, "dist": d})
		distances.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a["dist"] < b["dist"])

		for k: int in range(min(3, distances.size())):
			var ni: int = distances[k]["idx"]
			avg_pos += _fish_meshes[ni].position
			avg_vel += _fish_velocities[ni]
			neighbor_count += 1

		if neighbor_count > 0:
			avg_pos /= float(neighbor_count)
			avg_vel /= float(neighbor_count)
			var cohesion_force: Vector3 = (avg_pos - fish.position).normalized() * 0.5
			var alignment_force: Vector3 = avg_vel.normalized() * 0.3
			vel += (cohesion_force + alignment_force) * dt * 2.0

		# --- Global cohesion: return to school center ---
		var center_offset: Vector3 = fish.position
		if center_offset.length() > school_radius:
			vel += -center_offset.normalized() * 1.5 * dt * 4.0

		# --- Player avoidance ---
		if is_instance_valid(_player):
			var world_fish_pos: Vector3 = global_position + fish.position
			var dist_to_player: float = world_fish_pos.distance_to(_player.global_position)
			if dist_to_player < 3.0:
				var flee: Vector3 = (world_fish_pos - _player.global_position).normalized()
				vel += flee * 4.0 * dt * (3.0 - dist_to_player)

		# Clamp speed
		if vel.length() > swim_speed * 1.5:
			vel = vel.normalized() * swim_speed * 1.5
		if vel.length() < 0.3:
			vel = vel.normalized() * 0.3

		_fish_velocities[i] = vel


func _apply_velocities(delta: float) -> void:
	for i: int in range(_fish_meshes.size()):
		var fish: MeshInstance3D = _fish_meshes[i]
		var vel: Vector3 = _fish_velocities[i]
		fish.position += vel * delta

		# Rotate mesh to face velocity direction
		if vel.length_squared() > 0.001:
			var look_pos: Vector3 = fish.position + vel.normalized()
			fish.look_at(look_pos, Vector3.UP)
			# Compensate for the mesh's local rotation offset
			fish.rotation.z += PI * 0.5
