class_name PlayerDetectionZone
extends Node2D

@onready var circle_shape: Area2D = $CircleShape
@onready var collision_shape_2d: CollisionShape2D = $CircleShape/CollisionShape2D

@onready var polygon_shape: Area2D = $PolygonShape
@onready var collision_polygon_2d: CollisionPolygon2D = $PolygonShape/CollisionPolygon2D

@onready var sight_line: Polygon2D = $SightLine

var colshape_player : Node2D
var colpolyg_player : Node2D
## in degrees
@export var normal_viewing_angle : float = 40
## in degrees
@export var alerted_viewing_angle : float = 90

@export var normal_radius : float = 30
@export var alerted_radius : float = 50

var _sight_distance = Vector2(0, 0)
var player : Node2D
signal player_detected(body : Node2D)
signal player_lost(body : Node2D)

var is_player_lost : bool = false

var _current_direction = Vector2(0, 0)
var updating = false

var alerted_in_previous_frame : bool = false

func update(direction = Vector2(1, 0), alerted : bool = false):
	if alerted != alerted_in_previous_frame:
		collision_shape_2d.shape.radius = alerted_radius if alerted else normal_radius
	alerted_in_previous_frame = alerted
	if _current_direction == direction:
		return
	_current_direction = direction
	var viewport_size = get_viewport_rect().size / get_viewport().get_camera_2d().zoom
	var maximum = viewport_size/2 * Vector2(sign(_current_direction.x), sign(_current_direction.y))
	if _current_direction.x == 0 or _current_direction.y == 0:
		_sight_distance = maximum
	else:
		var y = (_current_direction.y * maximum.x)/_current_direction.x
		var x = (_current_direction.x * maximum.y)/_current_direction.y 
		_sight_distance = Vector2(min(abs(x), abs(maximum.x)), min(abs(y), abs(maximum.y))) * Vector2(sign(maximum.x), sign(maximum.y))
	var temp_polygon = collision_polygon_2d.get_polygon()
	var half_angle = (alerted_viewing_angle if alerted else normal_viewing_angle)/2
	temp_polygon[1] = _sight_distance.rotated(deg_to_rad(-half_angle))
	temp_polygon[2] = _sight_distance
	temp_polygon[3] = _sight_distance.rotated(deg_to_rad(half_angle))
	updating = true
	sight_line.call_deferred("set_polygon", temp_polygon)
	collision_polygon_2d.call_deferred("set_polygon", temp_polygon)
	if is_inside_tree():
		await get_tree().process_frame
	updating = false

func _process(_delta: float) -> void:
	player = colpolyg_player if colpolyg_player else colshape_player
	if player == null:
		return
	if !Geometry2D.is_point_in_circle(player.global_position, global_position, collision_shape_2d.shape.radius) and !Geometry2D.is_point_in_polygon(to_local(player.global_position), collision_polygon_2d.polygon):
		return
	if _is_detected(player):
		is_player_lost = false
		player_detected.emit(player)
	elif not is_player_lost:
		is_player_lost = true
		player_lost.emit(player)
	
func _is_detected(_player):
	var space_state = get_world_2d().direct_space_state
	var query = PhysicsRayQueryParameters2D.create(global_position, player.global_position, circle_shape.collision_mask)
	query.exclude = [self]
	query.collide_with_areas = true
	var result = space_state.intersect_ray(query)
	
	if result:
		if result.collider.is_in_group("Player"):
			return global_position.distance_to(result.collider.global_position) <= abs(_sight_distance.length())
	return false

func _on_circle_shape_body_entered(body: Node2D) -> void:
	if body is Area2D:
		return
	if body == null: 
		return
	if body.is_in_group("Player"):
		if updating:
			return
		colshape_player = body

func _on_circle_shape_area_entered(area: Area2D) -> void:
	if area == null: 
		return
	if area.name == "Hitbox":
		var body = area.get_parent()
		if body.is_in_group("Player"):
			if updating:
				return
			colshape_player = body

func _on_circle_shape_body_exited(body: Node2D) -> void:
	if body is Area2D:
		return
	if body == null: 
		return
	if body.is_in_group("Player") and body == colshape_player:
		if updating:
			return
		colshape_player = null
		if not colshape_player and not colpolyg_player:
			is_player_lost = true
			player_lost.emit(body)

func _on_circle_shape_area_exited(area: Area2D) -> void:
	if area == null: 
		return
	if area.name == "Hitbox":
		var body = area.get_parent()
		if body.is_in_group("Player") and body == colshape_player:
			if updating:
				return
			if body in circle_shape.get_overlapping_bodies():
				return
			colshape_player = null
			if not colshape_player and not colpolyg_player:
				is_player_lost = true
				player_lost.emit(body)

func _on_polygon_shape_body_entered(body: Node2D) -> void:
	if body is Area2D:
		return
	if body == null: 
		return
	if body.is_in_group("Player"):
		if updating:
			return
		colpolyg_player = body

func _on_polygon_shape_area_entered(area: Area2D) -> void:
	if area == null: 
		return
	if area.name == "Hitbox":
		var body = area.get_parent()
		if body.is_in_group("Player"):
			if updating:
				return
			colpolyg_player = body

func _on_polygon_shape_body_exited(body: Node2D) -> void:
	if body is Area2D:
		return
	if body == null: 
		return
	if body.is_in_group("Player") and body == colpolyg_player:
		if updating:
			return
		colpolyg_player = null
	if not colshape_player and not colpolyg_player:
			is_player_lost = true
			player_lost.emit(body)

func _on_polygon_shape_area_exited(area: Area2D) -> void:
	if area == null: 
		return
	if area.name == "Hitbox":
		var body = area.get_parent()
		if body.is_in_group("Player") and body == colpolyg_player:
			if updating:
				return
			if body in polygon_shape.get_overlapping_bodies():
				return
			colpolyg_player = null
		if not colshape_player and not colpolyg_player:
				is_player_lost = true
				player_lost.emit(body)
