class_name Hook
extends Node2D

## ================
## Export Field
## ================
@export var min_length := 140.0
@export var max_length := 200.0
@export var stretching_speed: float = 1400.0

@export_category("Hook Retract")
@export var retract_speed: float = 1800.0

@onready var line_2d: Line2D = %Line2D
@onready var ray: RayCast2D = %RayCast2D
@onready var tip_detector: Area2D = $TipDetector

var _tween: Tween

const GRAPABLE_GROUP = &"GRAPABLE"

signal stretching_finished(reach_limit: bool, anchor_node: Node2D)
## 钩爪击中物体信号，target 是被击中的物体，hit_pos 是击中点世界坐标，hook 是钩爪实例
signal hook_hit(target: Node2D, hit_pos: Vector2, hook: Hook)

## ================
## Private Field
## ================
var _binded_hook_comp
var _is_stretching := false
var _stretching_dir := Vector2.ZERO
var _cached_cancel := false
var _current_length := 0.0
var _anchor: Node2D

var _dir_id: int = -1

# =================
# Life Cycle
# =================

func _ready() -> void:
	ray.enabled = true
	ray.target_position = Vector2.ZERO

## 初始化
func init(hook_comp: SpawnHookComponet, reset_to_target: bool) -> void:
	_binded_hook_comp = hook_comp
	if reset_to_target:
		global_position = hook_comp.owner.global_position

# =================
# Stretch Control
# =================

func start_stretching(direction: Vector2) -> void:
	_is_stretching = true
	_cached_cancel = false
	_stretching_dir = direction.normalized()
	_current_length = 0.0
	_current_velocity = Vector2.ZERO  # 重置速度

	_dir_id = _get_direction_id(direction,8)

func end_stretching(force_end: bool = false) -> bool:
	if not force_end and _is_stretching:
		if _current_length < min_length:
			_cached_cancel = true
			return false

	_is_stretching = false
	_stretching_dir = Vector2.ZERO
	_current_velocity = Vector2.ZERO  # 重置速度
	return true

func is_stretching() -> bool:
	return _is_stretching

## 获取当前飞行方向（可被外部复写）
func get_stretching_dir() -> Vector2:
	return _stretching_dir.normalized()

## 设置飞行方向（供外部物体修改）
func set_stretching_dir(dir: Vector2) -> void:
	_stretching_dir = dir.normalized()
	_dir_id = _get_direction_id(_stretching_dir, 8)

# =================
# Update
# =================

func _physics_process(delta: float) -> void:
	if _is_stretching:
		_update_stretching(delta)

func _process(_delta: float) -> void:
	_update_line()

# =================
# Core Logic
# =================

## 钩爪当前速度
var _current_velocity: Vector2 = Vector2.ZERO
## 最大速度上限
@export var max_speed: float = 800.0

func _update_stretching(delta: float) -> void:
	# 检测前端点的吸引力 (direction, strength)
	var attract_config := _get_attraction_at(global_position + _current_velocity.normalized() * _current_length)

	# 初始速度（沿当前方向持续向前）
	if _current_velocity == Vector2.ZERO:
		_current_velocity = get_stretching_dir() * stretching_speed

	# 应用吸引力作为加速度
	if attract_config.strength > 0:
		var acceleration : Vector2 = attract_config.dir * attract_config.strength
		_current_velocity += acceleration * delta

	# 限制最大速度
	if _current_velocity.length() > max_speed:
		_current_velocity = _current_velocity.normalized() * max_speed

	# 基于更新后的速度预测“下一帧累计长度”
	var velocity_dir := _current_velocity.normalized()
	var velocity_mag := _current_velocity.length()

	var next_length := _current_length + velocity_mag * delta
	next_length = min(next_length, max_length)

	# 预测末端位置：起点 + 方向 * 累计长度
	var predicted_pos := global_position + velocity_dir * next_length

	tip_detector.global_position = predicted_pos

	# RayCast 也应该射到“累计长度”
	ray.target_position = velocity_dir * next_length
	ray.force_raycast_update()

	# ===== 命中檢測（最高優先）=====
	if ray.is_colliding():
		var collider := ray.get_collider()
		if collider is Node2D and collider.is_in_group(GRAPABLE_GROUP):
			var hit_pos := ray.get_collision_point()
			_current_length = global_position.distance_to(hit_pos)
			ray.target_position = velocity_dir * _current_length
			_handle_hit(collider as Node2D, hit_pos)
			return

	# ===== 没命中，才正式推進 =====
	_current_length = next_length

	# 取消邏輯
	if _cached_cancel and _current_length >= min_length:
		stretching_finished.emit(true, null)
		end_stretching(true)
		return

	# 超過最大距離
	if _current_length >= max_length:
		stretching_finished.emit(true, null)
		end_stretching(true)

## 在指定位置检测吸引力，返回 {"dir": Vector2, "strength": float}
func _get_attraction_at(pos: Vector2) -> Dictionary:
	var total_force := Vector2.ZERO

	# 检测 Areas
	for area in tip_detector.get_overlapping_areas():
		if area is Node2D and area.has_method("get_attraction_force"):
			total_force += area.get_attraction_force(pos)
		elif area.owner is Node2D and area.owner.has_method("get_attraction_force"):
			total_force += area.owner.get_attraction_force(pos)

	# 检测 Bodies
	for body in tip_detector.get_overlapping_bodies():
		if body is Node2D and body.has_method("get_attraction_force"):
			total_force += body.get_attraction_force(pos)
		elif body.owner is Node2D and body.owner.has_method("get_attraction_force"):
			total_force += body.owner.get_attraction_force(pos)

	# 返回 {"dir": direction, "strength": magnitude}
	if total_force != Vector2.ZERO:
		return {"dir": total_force.normalized(), "strength": total_force.length()}
	return {"dir": Vector2.ZERO, "strength": 0.0}

func _handle_hit(target: Node2D, hit_pos: Vector2) -> void:
	_is_stretching = false
	_stretching_dir = Vector2.ZERO
	_current_velocity = Vector2.ZERO  # 重置速度

	ray.target_position = to_local(hit_pos)

	# 如果 target 有 on_hook_hit 方法，调用它（传入钩爪实例）
	if target.has_method(&"on_hook_hit"):
		target.on_hook_hit(hit_pos, self)

	var reach_max := is_equal_approx(_current_length, max_length)
	var anchor := _create_anchor_on_node(target, hit_pos)

	stretching_finished.emit(reach_max, anchor)
	hook_hit.emit(target, hit_pos, self)

## 釋放鉤爪（清理 Anchor 與狀態）
func _release_hook() -> void:
	# 1. 停止拉伸（保險）
	_is_stretching = false
	_stretching_dir = Vector2.ZERO
	_current_velocity = Vector2.ZERO  # 重置速度
	_cached_cancel = false
	_current_length = 0.0

	# 2. 清掉 Anchor
	if _anchor and is_instance_valid(_anchor):
		# 先脫離父節點，避免殘留引用問題
		_anchor.get_parent().remove_child(_anchor)
		_anchor.queue_free()
		_anchor = null

	# 3. 重置 Ray 與 Line（視覺清乾淨）
	ray.target_position = Vector2.ZERO
	_update_line()

func release_hook_with_transition(has_trans: bool) -> void:
		# 先停止一切拉伸逻辑
	_is_stretching = false
	_stretching_dir = Vector2.ZERO
	_cached_cancel = false

	# 如果不需要动画，直接释放
	if not has_trans:
		_release_hook()
		queue_free()
		return

	# ===== 需要回收动画 =====
	# 先清 Anchor（但不影响末端视觉）
	if _anchor and is_instance_valid(_anchor):
		_anchor.get_parent().remove_child(_anchor)
		_anchor.queue_free()
		_anchor = null

	# 当前末端位置（本地坐标）
	var start_pos: Vector2 = ray.target_position
	var distance := start_pos.length()

	if distance <= 0.001:
		queue_free()
		return

	# 根据速度算 tween 时间
	var duration := distance / retract_speed

	if _tween and _tween.is_valid():
		_tween.kill()

	_tween = get_tree().create_tween()
	_tween.set_trans(Tween.TRANS_SINE)
	_tween.set_ease(Tween.EASE_IN)

	_tween.tween_property(
		ray,
		"target_position",
		Vector2.ZERO,
		duration
	)

	_tween.finished.connect(func():
		queue_free()
	)

func _update_line() -> void:
	if _anchor and is_instance_valid(_anchor):
		# 关键：锚点是世界坐标固定的，把它换算到 Hook 的本地坐标
		ray.target_position = to_local(_anchor.global_position)

	line_2d.set_point_position(1, ray.target_position)

func _create_anchor_on_node(target: Node2D, hit_global_pos: Vector2) -> Node2D:
	if _anchor and is_instance_valid(_anchor):
		_anchor.queue_free()

	_anchor = Node2D.new()
	_anchor.name = "Anchor"
	target.add_child(_anchor)

	# 關鍵：固定命中點偏移
	_anchor.position = target.to_local(hit_global_pos)

	return _anchor

## 计算方向id，私有
func _get_direction_id(direction: Vector2, sector_count: int) -> int:
	if direction == Vector2.ZERO:
		return -1

	var angle = rad_to_deg(direction.angle())

	angle += 90.0

	var sector_size = 360.0 / sector_count
	angle += sector_size / 2.0

	angle = fposmod(angle, 360.0)

	return int(angle / sector_size)

## 获取方向id
func get_direction_id() -> int:
	return _dir_id
