class_name MovementController
extends Node3D

enum State {
	IDLE,
	MOVING_LEFT,
	MOVING_RIGHT,
	MOVING_UP,
	MOVING_DOWN,
	GROWING,    # Moving up and still scaling up
	SHRINKING,  # Moving down while still scaling down
	FALLING     # Moving down due to gravity (not player input)
}

var current_state: State = State.IDLE

@onready var sprite_parents: Node3D = $"../../GraphicsNodes/SpriteParents"
@onready var current_state_label: Label3D = $"../../BubblyBody/CurrentState"
@onready var eyes_sprite: AnimatedSprite3D = $"../../Sprites/Eyes"
@onready var mouth_sprite: AnimatedSprite3D = $"../../Sprites/Mouth"
@onready var wind: AnimatedSprite3D = $"../../Sprites/Wind"

@export var target_node: Node3D

@export_group("Horizontal Movement")
@export var max_speed: float = 25.0
@export var acceleration: float = 60.0
@export var deceleration: float = 30.0

@export_group("Vertical Movement")
@export var gravity: float = 20.0
@export var max_vertical_speed: float = 8.0
@export var up_acceleration: float = 15.0
@export var up_deceleration: float = 25.0
@export var down_acceleration: float = 25.0
@export var down_deceleration: float = 15.0

@export_group("Scale Settings")
@export var min_scale: float = 0.6
@export var max_scale: float = 2
@export var scale_acceleration_time: float = 0.075
@export var scale_deceleration_time: float = 0.05

@export_group("Gravity")
@export var min_gravity: float = 0.0
@export var max_gravity: float = 30.0

var current_velocity: float = 0.0
var current_vertical_velocity: float = 0.0
var current_scale_tween: Tween
var target_scale: float = 1.0
var is_scaling: bool = false

func _ready() -> void:	
	if target_node:
		target_node.scale = Vector3(max_scale, max_scale, 1.0)
	
	if current_state_label:
		current_state_label.text = "IDLE"

func get_current_gravity() -> float:
	if not target_node:
		return max_gravity
		
	# Calculate how close we are to max_scale (0 = at min_scale, 1 = at max_scale)
	var current_scale := target_node.scale.x
	
	# Map the current scale to a 0-1 range where:
	# 0 = min_scale (maximum gravity)
	# 1 = max_scale (minimum gravity)
	var normalized_scale := inverse_lerp(min_scale, max_scale, current_scale)
	normalized_scale = clampf(normalized_scale, 0.0, 1.0)
	
	# Lerp between max_gravity and min_gravity based on scale
	return lerpf(max_gravity, min_gravity, normalized_scale)

func update_state() -> void:
	var input_direction: float = Input.get_axis("move_left", "move_right")
	var moving_up: bool = Input.is_action_pressed("move_up")
	var moving_down: bool = Input.is_action_pressed("move_down")
	
	# Check if we're at scale limits
	var _at_max_scale: bool = target_node and is_equal_approx(target_node.scale.x, max_scale)
	var _at_min_scale: bool = target_node and is_equal_approx(target_node.scale.x, min_scale)
	var above_min_scale: bool = target_node and target_node.scale.x > min_scale
	
	# Check if we're falling (moving down due to gravity)
	var is_falling := target_node.scale.x < (max_scale - min_scale) and not moving_down
	
	# Determine the new state
	if input_direction < 0:
		current_state = State.MOVING_LEFT
	elif input_direction > 0:
		current_state = State.MOVING_RIGHT
	elif moving_up:
		if target_node.scale.x < (max_scale * 0.9):
			current_state = State.GROWING
		else:
			current_state = State.MOVING_UP
	elif moving_down:
		if target_node.scale.x > (min_scale *1.2):
			current_state = State.SHRINKING
		else:
			current_state = State.MOVING_DOWN
	elif is_falling:
		current_state = State.FALLING
	else:
		current_state = State.IDLE
	
	# Update current state label for debugging
	if current_state_label:
		current_state_label.text = State.keys()[current_state]

func _physics_process(delta: float) -> void:
	update_state()
	handle_state()
	
	# Get input for both axes
	var input_direction := Vector2(
		Input.get_axis("move_left", "move_right"),
		Input.get_axis("move_down", "move_up")
	)
	
	# Normalize if moving diagonally
	if input_direction.length() > 1.0:
		input_direction = input_direction.normalized()
	
	handle_horizontal_movement(delta, input_direction.x)
	handle_vertical_movement(delta, input_direction.y)
	handle_scale_movement()
	
	# Move the target node
	if target_node:
		target_node.position.x += current_velocity * delta
		target_node.position.y += current_vertical_velocity * delta

func handle_horizontal_movement(delta: float, input_x: float) -> void:
	if input_x != 0:
		# Accelerate
		current_velocity = move_toward(current_velocity, 
			input_x * max_speed, 
			acceleration * delta)
		# Apply horizontal flip to sprite parents
		sprite_parents.scale.x = -1 if input_x < 0 else 1
	else:
		# Decelerate when no input
		current_velocity = move_toward(current_velocity, 
			0, 
			deceleration * delta)

func handle_vertical_movement(delta: float, input_y: float) -> void:
	if input_y > 0:  # Moving up
		# Accelerate upwards
		current_vertical_velocity = move_toward(current_vertical_velocity,
			max_vertical_speed,
			up_acceleration * delta)
	elif input_y < 0:  # Moving down
		# Accelerate downwards
		current_vertical_velocity = move_toward(current_vertical_velocity,
			-max_vertical_speed,
			down_acceleration * delta)
	else:
		# Apply gravity and appropriate deceleration with scale-based gravity
		var current_gravity := get_current_gravity()
		# Target velocity should also be affected by gravity
		var target_velocity := -max_vertical_speed * (current_gravity / max_gravity)
		var decel := down_acceleration if current_vertical_velocity < 0 else up_deceleration
		current_vertical_velocity = move_toward(current_vertical_velocity,
			target_velocity,
			(decel + current_gravity) * delta)

func handle_scale_movement() -> void:
	if not target_node:
		return
		
	var moving_up: bool = Input.is_action_pressed("move_up")
	var moving_down: bool = Input.is_action_pressed("move_down")
	
	# Kill existing tween if direction changes or movement stops
	if current_scale_tween and current_scale_tween.is_valid():
		if (moving_up and target_scale < 1.0) or (moving_down and target_scale > 1.0) or (!moving_up and !moving_down):
			current_scale_tween.kill()
	
	if moving_up:
		start_scale_tween(max_scale)
	elif moving_down:
		start_scale_tween(min_scale)
	elif not is_scaling:
		start_scale_tween(1.0)

func start_scale_tween(new_target_scale: float) -> void:
	# Only skip if we're already at the target scale
	if target_node and is_equal_approx(target_node.scale.x, new_target_scale):
		return
		
	target_scale = new_target_scale
	
	if current_scale_tween and current_scale_tween.is_valid():
		current_scale_tween.kill()
	
	current_scale_tween = create_tween()
	current_scale_tween.set_trans(Tween.TRANS_QUAD)
	current_scale_tween.set_ease(Tween.EASE_IN_OUT)
	
	var duration := scale_acceleration_time if target_scale != 1.0 else scale_deceleration_time
	is_scaling = true
	
	# Create a target scale that preserves Z at 1
	var target_scale_vec = Vector3(target_scale, target_scale, 1.0)
	current_scale_tween.tween_property(target_node, "scale", target_scale_vec, duration)
	current_scale_tween.finished.connect(func(): is_scaling = false)

func handle_state() -> void:
	if current_state == State.IDLE:
		eyes_sprite.play("neutral")
		mouth_sprite.play("neutral")
		wind.visible = false
	elif current_state == State.MOVING_LEFT:
		pass
	elif current_state == State.MOVING_RIGHT:
		pass
	elif current_state == State.MOVING_UP:
		eyes_sprite.play("moving_up")
		mouth_sprite.play("moving_up")
		wind.visible = false
	elif current_state == State.MOVING_DOWN:
		eyes_sprite.play("moving_down")
		mouth_sprite.play("moving_down")
		wind.visible = false
	elif current_state == State.GROWING:
		eyes_sprite.play("growing")
		mouth_sprite.play("growing")
		wind.visible = true
		wind.play("growing")
	elif current_state == State.SHRINKING:
		eyes_sprite.play("shrinking")
		mouth_sprite.play("shrinking")
		wind.visible = true
		wind.play("shrinking")
	elif current_state == State.FALLING:
		eyes_sprite.play("falling")
		mouth_sprite.play("falling")
		wind.visible = false
