extends Node

const DEBUG_LOGS := true

@export var npc_visual_scene: PackedScene
@export var simulation_manager: Node
@export var active_npcs_parent: Node3D

@export var guard_zone: Marker3D
@export var study_zone: Marker3D
@export var rest_zone: Marker3D
@export var food_zone: Marker3D

var active_npc_visuals := {}

func debug_log(message: String) -> void:
	if DEBUG_LOGS:
		print("[WorldViewManager] ", message)

func _ready() -> void:
	call_deferred("initialize_world_view")

func initialize_world_view() -> void:
	await get_tree().physics_frame
	spawn_initial_npcs()
	
	if simulation_manager.has_signal("npc_task_changed"):
		simulation_manager.npc_task_changed.connect(_on_npc_task_changed)
	else:
		push_error("WorldViewManager: SimulationManager has no npc_task_changed signal")

	if simulation_manager.has_signal("npc_died"):
		simulation_manager.npc_died.connect(_on_npc_died)
	else:
		push_error("WorldViewManager: SimulationManager has no npc_died signal")
	
	update_npc_targets()

func spawn_initial_npcs() -> void:
	if simulation_manager == null:
		push_error("WorldViewManager: simulation_manager is missing")
		return

	if npc_visual_scene == null:
		push_error("WorldViewManager: npc_visual_scene is missing")
		return

	if active_npcs_parent == null:
		push_error("WorldViewManager: active_npcs_parent is missing")
		return

	debug_log("Spawning NPC visuals. Count: %s" % simulation_manager.npcs.size())

	for npc in simulation_manager.npcs:
		spawn_npc_visual(npc)

func spawn_npc_visual(npc: SimNPC) -> void:
	if active_npc_visuals.has(npc.id):
		return

	var visual = npc_visual_scene.instantiate()
	active_npcs_parent.add_child(visual)

	visual.global_position = npc.position
	visual.setup_from_sim(npc)

	if visual.has_signal("arrived_at_target"):
		visual.arrived_at_target.connect(_on_npc_visual_arrived)
	else:
		push_error("WorldViewManager: NPCVisual has no arrived_at_target signal")

	if visual.has_signal("navigation_failed"):
		visual.navigation_failed.connect(_on_npc_visual_navigation_failed)
	else:
		push_error("WorldViewManager: NPCVisual has no navigation_failed signal")

	active_npc_visuals[npc.id] = visual
	debug_log("Spawned visual for %s at %s" % [npc.npc_name, npc.position])

func update_npc_targets() -> void:
	if simulation_manager == null:
		return

	for npc in simulation_manager.npcs:
		if not active_npc_visuals.has(npc.id):
			continue

		var visual = active_npc_visuals[npc.id]
		var task = npc.current_task
		if task in [
			&"",
			SimulationIds.ACTION_IDLE,
			SimulationIds.ACTION_DEAD
		]:
			continue

		var resource_pos = _try_get_resource_target(npc, npc.current_task)
		if resource_pos != null:
			visual.set_target_position(resource_pos)
			continue

		if task == SimulationIds.ACTION_WANDER:
			var offset := _get_wander_offset(npc.id)
			visual.set_target_position(visual.global_position + offset)
			continue

		var definition := SimulationDefinitions.get_action(task)
		if (
			definition != null
			and definition.target_type == SimulationIds.TARGET_RESOURCE
		):
			if simulation_manager.has_method("notify_npc_target_unavailable"):
				simulation_manager.notify_npc_target_unavailable(npc.id)
			continue

		var target = get_target_for_task(task)

		if target == null:
			debug_log("No target for task: %s" % task)
			continue

		visual.set_target_position(target.global_position)

func get_target_for_task(task: StringName) -> Marker3D:
	match task:
		SimulationIds.ACTION_PATROL:
			return guard_zone
		SimulationIds.ACTION_STUDY:
			return study_zone
		SimulationIds.ACTION_REST:
			return rest_zone
		SimulationIds.ACTION_EAT:
			return food_zone
		_:
			return rest_zone

func _try_get_resource_target(npc: SimNPC, task: StringName) -> Variant:
	var definition := SimulationDefinitions.get_action(task)
	if (
		definition == null
		or definition.target_type != SimulationIds.TARGET_RESOURCE
	):
		return null

	var visual = active_npc_visuals.get(npc.id) as Node3D
	if visual == null:
		return null

	if not simulation_manager.has_method("acquire_resource_target"):
		push_error("WorldViewManager: SimulationManager cannot acquire resources")
		return null
	var node: ResourceNode = simulation_manager.acquire_resource_target(
		definition.resource_action_id,
		npc.id,
		visual.global_position
	)
	if node == null:
		debug_log("No available resource node for %s" % task)
		return null

	npc.target_id = node.node_id
	debug_log("%s reserved %s for %s" % [npc.npc_name, node.node_id, task])
	return node.interaction_point.global_position

func _on_npc_task_changed(
	npc: SimNPC,
	old_task: StringName,
	new_task: StringName
) -> void:
	if DEBUG_LOGS:
		debug_log("%s changed task: %s -> %s" % [npc.npc_name, old_task, new_task])
	
	if not active_npc_visuals.has(npc.id):
		return

	if new_task in [
		&"",
		SimulationIds.ACTION_IDLE,
		SimulationIds.ACTION_DEAD
	]:
		return
	
	var visual = active_npc_visuals[npc.id]
	var resource_pos = _try_get_resource_target(npc, new_task)
	if resource_pos != null:
		visual.set_target_position(resource_pos)
		return
	
	if new_task == SimulationIds.ACTION_WANDER:
		var offset := _get_wander_offset(npc.id)
		visual.set_target_position(visual.global_position + offset)
		return
	
	var definition := SimulationDefinitions.get_action(new_task)
	if (
		definition != null
		and definition.target_type == SimulationIds.TARGET_RESOURCE
	):
		if simulation_manager.has_method("notify_npc_target_unavailable"):
			simulation_manager.notify_npc_target_unavailable(npc.id)
		else:
			push_error("WorldViewManager: SimulationManager cannot handle unavailable targets")
		return
	
	var target := get_target_for_task(new_task)
	
	if target != null:
		visual.set_target_position(target.global_position)

func _get_wander_offset(npc_id: int) -> Vector3:
	if simulation_manager != null and simulation_manager.has_method("get_wander_offset"):
		return simulation_manager.get_wander_offset(npc_id)
	push_error("WorldViewManager: SimulationManager cannot provide deterministic wander offsets")
	return Vector3.ZERO
		
func _on_npc_visual_arrived(sim_id: int) -> void:
	debug_log("NPC visual arrived. sim_id=%s" % sim_id)

	if simulation_manager == null:
		push_error("WorldViewManager: simulation_manager missing during arrival report")
		return

	if simulation_manager.has_method("notify_npc_arrived"):
		simulation_manager.notify_npc_arrived(sim_id)
	else:
		push_error("WorldViewManager: SimulationManager has no notify_npc_arrived method")

func _on_npc_visual_navigation_failed(sim_id: int) -> void:
	debug_log("NPC visual navigation failed. sim_id=%s" % sim_id)

	if simulation_manager == null:
		push_error("WorldViewManager: simulation_manager missing during navigation failure")
		return

	if simulation_manager.has_method("notify_npc_navigation_failed"):
		simulation_manager.notify_npc_navigation_failed(sim_id)
	else:
		push_error("WorldViewManager: SimulationManager has no notify_npc_navigation_failed method")

func _on_npc_died(npc: SimNPC) -> void:
	if not active_npc_visuals.has(npc.id):
		return

	var visual = active_npc_visuals[npc.id]

	if visual.has_method("apply_dead_visual_state"):
		visual.apply_dead_visual_state()
	else:
		push_error("WorldViewManager: NPCVisual has no apply_dead_visual_state method")
