## A draggable object that supports mouse interaction with state-based animation system.
##
## This class provides a robust state machine for handling mouse interactions including
## hover effects, drag operations, and programmatic movement using Tween animations.
## All interactive cards and objects extend this base class to inherit consistent
## drag-and-drop behavior.
##
## Key Features:
## - State machine with safe transitions (IDLE → HOVERING → HOLDING → MOVING)
## - Tween-based animations for smooth hover effects and movement
## - Mouse interaction handling with proper event management
## - Z-index management for visual layering during interactions
## - Extensible design with virtual methods for customization
##
## State Transitions:
## - IDLE: Default state, ready for interaction
## - HOVERING: Mouse over with visual feedback (scale, rotation, position)
## - HOLDING: Active drag state following mouse movement
## - MOVING: Programmatic movement ignoring user input
##
## Usage:
## [codeblock]
## class_name MyDraggable
## extends DraggableObject
##
## func _can_start_hovering() -> bool:
##     return my_custom_condition
## [/codeblock]
class_name DraggableObject
extends Control

# Enums
## Enumeration of possible interaction states for the draggable object.
enum DraggableState {
	IDLE,       ## Default state - no interaction
	HOVERING,   ## Mouse over state - visual feedback
	HOLDING,    ## Dragging state - follows mouse
	MOVING      ## Programmatic move state - ignores input
}

## The speed at which the objects moves.
@export var moving_speed: int = CardFrameworkSettings.ANIMATION_MOVE_SPEED
## Whether the object can be interacted with.
@export var can_be_interacted_with: bool = true
## The distance the object hovers when interacted with.
@export var hover_distance: int = CardFrameworkSettings.PHYSICS_HOVER_DISTANCE
## The scale multiplier when hovering.
@export var hover_scale: float = CardFrameworkSettings.ANIMATION_HOVER_SCALE
## The rotation in degrees when hovering.
@export var hover_rotation: float = CardFrameworkSettings.ANIMATION_HOVER_ROTATION
## The duration for hover animations.
@export var hover_duration: float = CardFrameworkSettings.ANIMATION_HOVER_DURATION


# Legacy variables - kept for compatibility but no longer used in state machine
var is_pressed: bool = false
var is_holding: bool = false
var stored_z_index: int:
	set(value):
		z_index = value
		stored_z_index = value
# State Machine
var current_state: DraggableState = DraggableState.IDLE

# Mouse tracking
var is_mouse_inside: bool = false

# Movement state tracking
var is_moving_to_destination: bool = false
var is_returning_to_original: bool = false

# Position and animation tracking
var current_holding_mouse_position: Vector2
var original_position: Vector2
var original_scale: Vector2
var original_hover_rotation: float
var current_hover_position: Vector2  # Track position during hover animation

# Move operation tracking
var target_destination: Vector2  # Target position passed to move() function
var target_rotation: float       # Target rotation passed to move() function
var original_destination: Vector2
var original_rotation: float
var destination_degree: float

# Tween objects
var move_tween: Tween
var hover_tween: Tween

# State transition rules
var allowed_transitions = {
	DraggableState.IDLE: [DraggableState.HOVERING, DraggableState.HOLDING, DraggableState.MOVING],
	DraggableState.HOVERING: [DraggableState.IDLE, DraggableState.HOLDING, DraggableState.MOVING],
	DraggableState.HOLDING: [DraggableState.IDLE, DraggableState.MOVING],
	DraggableState.MOVING: [DraggableState.IDLE]
}


func _ready() -> void:
	mouse_filter = Control.MOUSE_FILTER_STOP
	connect("mouse_entered", _on_mouse_enter)
	connect("mouse_exited", _on_mouse_exit)
	connect("gui_input", _on_gui_input)
	
	original_destination = global_position
	original_rotation = rotation
	original_position = position
	original_scale = scale
	original_hover_rotation = rotation
	stored_z_index = z_index


## Safely transitions between interaction states using predefined rules.
## Validates transitions and handles state cleanup/initialization automatically.
## @param new_state: Target state to transition to
## @returns: True if transition was successful, false if invalid/blocked
func change_state(new_state: DraggableState) -> bool:
	if new_state == current_state:
		return true
	
	# Validate transition is allowed by state machine rules
	if not new_state in allowed_transitions[current_state]:
		return false
	
	# Clean up previous state
	_exit_state(current_state)
	
	var old_state = current_state
	current_state = new_state
	
	# Enter new state
	_enter_state(new_state, old_state)
	
	return true


# Handle state entry
func _enter_state(state: DraggableState, from_state: DraggableState) -> void:
	match state:
		DraggableState.IDLE:
			z_index = stored_z_index
			mouse_filter = Control.MOUSE_FILTER_STOP
			
		DraggableState.HOVERING:
			# z_index = stored_z_index + CardFrameworkSettings.VISUAL_DRAG_Z_OFFSET
			_start_hover_animation()
			
		DraggableState.HOLDING:
			# Preserve hover position if transitioning from HOVERING state
			if from_state == DraggableState.HOVERING:
				_preserve_hover_position()
			# For IDLE → HOLDING transitions, current position is maintained
			
			current_holding_mouse_position = get_local_mouse_position()
			z_index = stored_z_index + CardFrameworkSettings.VISUAL_DRAG_Z_OFFSET
			rotation = 0
			
		DraggableState.MOVING:
			# Stop hover animations and ignore input during programmatic movement
			if hover_tween and hover_tween.is_valid():
				hover_tween.kill()
				hover_tween = null
			z_index = stored_z_index + CardFrameworkSettings.VISUAL_DRAG_Z_OFFSET
			mouse_filter = Control.MOUSE_FILTER_IGNORE


# Handle state exit
func _exit_state(state: DraggableState) -> void:
	match state:
		DraggableState.HOVERING:
			z_index = stored_z_index
			_stop_hover_animation()
			
		DraggableState.HOLDING:
			z_index = stored_z_index
			# Reset visual effects but preserve position for return_card() animation
			scale = original_scale
			rotation = original_hover_rotation
			
		DraggableState.MOVING:
			mouse_filter = Control.MOUSE_FILTER_STOP


func _process(delta: float) -> void:
	match current_state:
		DraggableState.HOLDING:
			global_position = get_global_mouse_position() - current_holding_mouse_position


func _finish_move() -> void:
	# Complete movement processing
	is_moving_to_destination = false
	rotation = destination_degree
	
	# Update original position and rotation only when not returning to original
	# Important: Use original target values from move() instead of global_position
	if not is_returning_to_original:
		original_destination = target_destination
		original_rotation = target_rotation
	
	# Reset return flag
	is_returning_to_original = false
	
	# End MOVING state - return to IDLE
	change_state(DraggableState.IDLE)
	
	# Call inherited class callback
	_on_move_done()


func _on_move_done() -> void:
	# This function can be overridden by subclasses to handle when the move is done.
	pass


# Start hover animation with tween
func _start_hover_animation() -> void:
	# Stop any existing hover animation
	if hover_tween and hover_tween.is_valid():
		hover_tween.kill()
		hover_tween = null
		position = original_position  # Reset position to original before starting new hover
		scale = original_scale
		rotation = original_hover_rotation

	# Update original position to current position (important for correct return)
	original_position = position
	original_scale = scale
	original_hover_rotation = rotation
	
	# Store current position before animation
	current_hover_position = position
	
	# Create new hover tween
	hover_tween = create_tween()
	hover_tween.set_parallel(true)  # Allow multiple properties to animate simultaneously
	
	# Animate position (hover up)
	var target_position = Vector2(position.x - 30, position.y - hover_distance)
	hover_tween.tween_property(self, "position", target_position, hover_duration)
	
	# Animate scale
	hover_tween.tween_property(self, "scale", original_scale * hover_scale, hover_duration)
	
	# Animate rotation
	#hover_tween.tween_property(self, "rotation", deg_to_rad(hover_rotation), hover_duration)
	
	# Update current hover position tracking
	hover_tween.tween_method(_update_hover_position, position, target_position, hover_duration)


# Stop hover animation and return to original state
func _stop_hover_animation() -> void:
	# Stop any existing hover animation
	if hover_tween and hover_tween.is_valid():
		hover_tween.kill()
		hover_tween = null
	
	# Create new tween to return to original state
	hover_tween = create_tween()
	hover_tween.set_parallel(true)
	
	# Animate back to original position
	hover_tween.tween_property(self, "position", original_position, hover_duration)
	
	# Animate back to original scale
	hover_tween.tween_property(self, "scale", original_scale, hover_duration)
	
	# Animate back to original rotation
	hover_tween.tween_property(self, "rotation", original_hover_rotation, hover_duration)
	
	# Update current hover position tracking
	hover_tween.tween_method(_update_hover_position, position, original_position, hover_duration)


# Track current position during hover animation for smooth HOLDING transition
func _update_hover_position(pos: Vector2) -> void:
	current_hover_position = pos


# Preserve current hover position when transitioning to HOLDING
func _preserve_hover_position() -> void:
	# Stop hover animation and preserve current position
	if hover_tween and hover_tween.is_valid():
		hover_tween.kill()
		hover_tween = null
	
	# Explicitly set position to current hover position
	# This ensures smooth transition from hover animation to holding
	position = current_hover_position


## Virtual method to determine if hovering animation can start.
## Override in subclasses to implement custom hovering conditions.
## @returns: True if hovering is allowed, false otherwise
func _can_start_hovering() -> bool:
	return true


func _on_mouse_enter() -> void:
	is_mouse_inside = true
	if can_be_interacted_with and _can_start_hovering():
		change_state(DraggableState.HOVERING)


func _on_mouse_exit() -> void:
	is_mouse_inside = false
	match current_state:
		DraggableState.HOVERING:
			change_state(DraggableState.IDLE)


func _on_gui_input(event: InputEvent) -> void:
	if not can_be_interacted_with:
		return
	
	if event is InputEventMouseButton:
		_handle_mouse_button(event as InputEventMouseButton)


## Moves the object to target position with optional rotation using smooth animation.
## Automatically transitions to MOVING state and handles animation timing based on distance.
## @param target_destination: Global position to move to
## @param degree: Target rotation in radians
func move(target_destination: Vector2, degree: float) -> void:
	# Skip if current position and rotation match target
	if global_position == target_destination and rotation == degree:
		return

	# Force transition to MOVING state (highest priority)
	change_state(DraggableState.MOVING)

	# Stop existing movement
	if move_tween and move_tween.is_valid():
		move_tween.kill()
		move_tween = null
	
	# Store target position and rotation for original value preservation
	self.target_destination = target_destination
	self.target_rotation = degree
	
	# Initial setup
	rotation = 0
	destination_degree = degree
	is_moving_to_destination = true
	
	# Smooth Tween-based movement with dynamic duration based on moving_speed
	var distance = global_position.distance_to(target_destination)
	var duration = distance / moving_speed
	
	move_tween = create_tween()
	move_tween.tween_property(self, "global_position", target_destination, duration)
	move_tween.tween_callback(_finish_move)


func _handle_mouse_button(mouse_event: InputEventMouseButton) -> void:
	if mouse_event.button_index != MOUSE_BUTTON_LEFT:
		return
	
	# Ignore all input during MOVING state
	if current_state == DraggableState.MOVING:
		return
	
	if mouse_event.is_pressed():
		_handle_mouse_pressed()
	
	if mouse_event.is_released():
		_handle_mouse_released()


## Returns the object to its original position with smooth animation.
func return_to_original() -> void:
	is_returning_to_original = true
	move(original_destination, original_rotation)


func _handle_mouse_pressed() -> void:
	is_pressed = true
	match current_state:
		DraggableState.HOVERING:
			change_state(DraggableState.HOLDING)
		DraggableState.IDLE:
			if is_mouse_inside and can_be_interacted_with and _can_start_hovering():
				change_state(DraggableState.HOLDING)


func _handle_mouse_released() -> void:
	is_pressed = false
	match current_state:
		DraggableState.HOLDING:
			change_state(DraggableState.IDLE)
