'''移动控制组件
		注：该组件只适用于继承自CharaterBody2D的角色。
		
		基础移动控制：
			
		外部调用移动：
			add_movement_input()
			stop_movement() 停止运动，可以根据输入的Flag来决定是否要完全打断运动
		
		内部调用：
			speed_approach() 此函数将当前速度趋近向目标速度
'''
class_name LocomotionComponent extends ComponentBase

@export_category("Component Setting")
##如果设置为false，则角色将完全不能移动
@export var _can_move : bool = true

@export_category("Gravity Properties")
@export_subgroup("Fall")
##角色受到的重力乘量
@export var default_gravity_scale: float = 1.0
##如果设置为false，则角色将不会应用重力
@export var should_apply_gravity : bool = true
##角色下落的最大速度
@export var fall_maxium_speed : float = 480
##当空中速度超过了阈值速度时，降速的加速度
@export var fall_reduce_acceleration: float = 4000
##空中控制乘量，在空中移动时修改
@export var air_control_mult : float = .65
#基础的乘量，对所有Character相同
const GRAVITY_BASIC_MULT_FACTOR : float = 1.8
#下坠速度和最大下坠速度的阈值，超过了这个阈值会开启下坠速度修正，主要取决于设备的刷新率固写死。
const FALL_SPEED_EXCEED_TOLERANCE_THRESHOLD = 40

@export_category("Locomotion Properties")
@export_subgroup("Move")
##存在輸入時，向最大移動輸入運動的加速度
@export var run_accel : float = 1200
##不存在輸入時，向Vector.ZERO運動的加速度
@export var run_reduce : float = 1600
##移動時的最大速度
@export var move_speed_max : float = 280

var characterbody: CharacterBody2D

var _movement_input : float = 0.0
var _was_moving : bool = false
var _in_pivoting : bool = false
var last_frame_character_on_floor: bool = false
var is_first_update: bool = false

signal move_dir_changed_in_moving(direction : float)
signal start_move(direction : float)
signal ground_state_changed(is_leave: bool)

func _init_component() -> void:
	characterbody = component_owner as CharacterBody2D
	assert(characterbody,"组件没有正确的绑定CharacterBody2D")

#region 外部调用函数
##外部每帧调用这个方法，以让角色移动
func add_movement_input(input : float) -> void:
	_movement_input = input

#外部调用这个函数，强制让角色停住
func stop_movement(force_static: bool = false) -> void:
	if force_static:						#如果希望角色强制停下，则设置为真
		characterbody.velocity = Vector2.ZERO
		characterbody.move_and_slide()
	# 立即重置输入状态
	_movement_input = 0.0

func suspend_movement() -> void:
	characterbody.velocity = Vector2.ZERO
	characterbody.move_and_slide()
	_can_move = false

func enable_movement() -> void:
	_can_move = true

#endregion

func _physics_process(delta: float) -> void:
	if !_can_move : 
		return #不允许移动，直接停止退出
	
	_update_movement(delta) #更新移动输入
	_update_gravity(delta) #更新重力
	
	characterbody.move_and_slide() #移动更新
	_handle_body_ground_state(delta)

##更新重力相关的函数
func _update_gravity(delta: float) -> void:
	
	var speed_on_gravity_dir = characterbody.velocity.dot(characterbody.get_gravity().normalized())
	if should_apply_gravity and speed_on_gravity_dir <= _get_max_fall_speed(): 
		characterbody.velocity += characterbody.get_gravity() * GRAVITY_BASIC_MULT_FACTOR * _get_gravity_scale() * delta
	
	if (abs(characterbody.velocity.dot(characterbody.get_gravity().normalized()))- _get_max_fall_speed()) > FALL_SPEED_EXCEED_TOLERANCE_THRESHOLD:
		characterbody.velocity = velocity_approach(characterbody.velocity,characterbody.get_gravity().normalized() * _get_max_fall_speed(), fall_reduce_acceleration * delta)

##更新移动相关的函数
func _update_movement(delta : float) -> void:
	
	var input_dir = sign(_movement_input) as float
	var current_dir = sign(characterbody.velocity.x) as float
	var accel: float  
	if abs(characterbody.velocity.x) > move_speed_max or \
		input_dir * current_dir < 0 or \
		(input_dir == 0 and abs(characterbody.velocity.x) > 0):
		#如果：
		# 1. 速度超过极限速度
		# 2. 移动方向和输入方向相反
		# 3. 输入为0，但仍然有速度。(放开输入，等待停止)
		#那么apply 减速的加速度。
		accel = run_reduce 
	else:
		accel = run_accel #其他case都是加速的加速度
	
	#如果检测到当前速度和加速度方向不同，则发送移动方向更改event
	if current_dir * input_dir < 0 and not _in_pivoting:
		move_dir_changed_in_moving.emit(input_dir)
		_in_pivoting  = true
	
	if current_dir * input_dir >= 0:
		_in_pivoting = false
	
	var applyed_air_control = 1 if characterbody.is_on_floor() else air_control_mult
	var target_move_speed = move_speed_max * input_dir * applyed_air_control
	
	characterbody.velocity.x = speed_approach(characterbody.velocity.x,target_move_speed,accel * delta)
	
	#检测是否这帧开始了移动
	_check_is_start_move()

##用来检测玩家是否当前帧改变了状态。
func _handle_body_ground_state(delta: float) -> void:
	if is_first_update:
		is_first_update = false
		last_frame_character_on_floor = characterbody.is_on_floor()
		return
	
	if last_frame_character_on_floor != characterbody.is_on_floor():
		ground_state_changed.emit(last_frame_character_on_floor)
	last_frame_character_on_floor = characterbody.is_on_floor()

func _get_gravity_scale() -> float:
	return default_gravity_scale

func _get_max_fall_speed() -> float:
	return fall_maxium_speed

# -----------HELPER-----------
##将一个速度持续Lerp向目标速度
func speed_approach(current: float, target: float, delta: float) -> float:
	if current < target:
		return min(current + delta, target)
	elif current > target:
		return max(current - delta, target)
	return target

##将一个向量速度持续lerp向目标向量速度
func velocity_approach(current: Vector2, target: Vector2, delta: float) -> Vector2:
	var result = current
	result.x = speed_approach(current.x, target.x, delta)
	result.y = speed_approach(current.y, target.y, delta)
	return result

##用于检测是否开始移动
func _check_is_start_move() -> void:
	# 判断是否开始移动
	var is_moving_now = not is_zero_approx(characterbody.velocity.x) 

	if is_moving_now and not _was_moving:
		start_move.emit(sign(characterbody.velocity.x)) # 发送开始移动事件

	_was_moving = is_moving_now
# -----------HELPER-----------
