class_name BubbleButton
extends Button

@export var profile: BubbleMenuProfile

@export_group("Authored Layout")
@export var neutral_size: Vector2 = Vector2(120.0, 116.0)
@export var desktop_anchor: Vector2 = Vector2.ZERO
@export var compact_anchor: Vector2 = Vector2.ZERO
@export var compact_minimum_size: Vector2 = Vector2.ZERO

@export_group("Typography")
@export var label_control_path: NodePath
@export_range(1, 256, 1) var minimum_font_size: int = 14
@export_range(1, 256, 1) var maximum_font_size: int = 32

@export_group("Motion")
@export_range(0.0, 32.0, 0.1) var horizontal_amplitude: float = 1.8
@export_range(0.0, 32.0, 0.1) var vertical_amplitude: float = 4.0
@export_range(0.1, 30.0, 0.1) var motion_period: float = 5.0
@export_range(0.0, TAU, 0.01) var motion_phase: float = 0.0
@export_range(0.0, 0.2, 0.001) var deformation_amplitude: float = 0.016
@export_range(0.1, 30.0, 0.1) var deformation_period: float = 6.0

var neutral_position: Vector2 = Vector2.ZERO
var presented_size: Vector2 = Vector2.ZERO
var emphasis: float = 0.0
var _hovered: bool = false
var _focused: bool = false


func _ready() -> void:
	mouse_entered.connect(_set_hovered.bind(true))
	mouse_exited.connect(_set_hovered.bind(false))
	focus_entered.connect(_set_focused.bind(true))
	focus_exited.connect(_set_focused.bind(false))
	resized.connect(_update_pivot)
	_update_pivot()
	apply_profile()


func apply_layout(
	center: Vector2,
	bubble_size: Vector2,
	font_size_ratio: float,
) -> void:
	position = center - bubble_size * 0.5
	size = bubble_size
	neutral_position = position
	presented_size = bubble_size
	_update_pivot()
	var label_control: Control = get_label_control()
	var font_size := clampi(
		roundi(minf(bubble_size.x, bubble_size.y) * font_size_ratio),
		minimum_font_size,
		maximum_font_size
	)
	label_control.add_theme_font_size_override("font_size", font_size)


func get_layout_size(layout_scale: float, compact: bool) -> Vector2:
	var bubble_size: Vector2 = neutral_size * layout_scale
	if compact:
		bubble_size.x = maxf(bubble_size.x, compact_minimum_size.x)
		bubble_size.y = maxf(bubble_size.y, compact_minimum_size.y)
	return bubble_size


func get_authored_anchor(compact: bool) -> Vector2:
	return compact_anchor if compact else desktop_anchor


func get_label_control() -> Control:
	if not label_control_path.is_empty():
		var custom_label := get_node_or_null(label_control_path) as Control
		if custom_label != null:
			return custom_label
	return self


func advance_emphasis(delta: float) -> void:
	var target: float = 1.0 if _hovered or _focused else 0.0
	emphasis = move_toward(
		emphasis,
		target,
		profile.emphasis_speed * delta
	)


func calculate_target(
	elapsed: float,
	layout_scale: float,
	motion_scale: float,
) -> Vector2:
	var phase: float = (
		elapsed / motion_period * TAU
		+ motion_phase
	)
	var idle_offset := Vector2(
		sin(phase * 0.73 + motion_phase) * horizontal_amplitude,
		sin(phase) * vertical_amplitude
	) * layout_scale * motion_scale
	idle_offset.y -= (
		profile.hover_focus_lift
		* emphasis
		* layout_scale
	)
	return neutral_position + idle_offset


func calculate_visual_scale(
	elapsed: float,
	motion_scale: float,
) -> Vector2:
	var deformation_phase: float = (
		elapsed / deformation_period * TAU
		+ motion_phase * 1.37
	)
	var deformation_amount: float = (
		sin(deformation_phase)
		* deformation_amplitude
		* motion_scale
		* lerpf(1.0, profile.emphasized_deformation_scale, emphasis)
	)
	var hover_scale: float = lerpf(
		1.0,
		profile.hover_focus_scale,
		emphasis
	)
	return Vector2(
		1.0 + deformation_amount,
		1.0 - deformation_amount
	) * hover_scale


func get_visual_radius(visual_scale: Vector2) -> float:
	var visual_size: Vector2 = presented_size * visual_scale
	return (visual_size.x + visual_size.y) * 0.25


func apply_presentation(
	target_position: Vector2,
	visual_scale: Vector2,
	position_weight: float,
) -> void:
	position = position.lerp(target_position, position_weight)
	scale = visual_scale


func _has_point(point: Vector2) -> bool:
	var radius: Vector2 = size * 0.5
	if radius.x <= 0.0 or radius.y <= 0.0:
		return false
	var normalized: Vector2 = (point - radius) / radius
	return normalized.length_squared() <= 1.0


func _set_hovered(value: bool) -> void:
	_hovered = value


func _set_focused(value: bool) -> void:
	_focused = value


func _update_pivot() -> void:
	pivot_offset = size * 0.5


func apply_profile() -> void:
	if profile == null:
		return
	add_theme_stylebox_override("normal", profile.make_normal_style())
	var hover_style: StyleBoxFlat = profile.make_hover_style()
	add_theme_stylebox_override("hover", hover_style)
	add_theme_stylebox_override("focus", hover_style)
	add_theme_stylebox_override("pressed", profile.make_pressed_style())
	add_theme_stylebox_override("disabled", profile.make_disabled_style())
	add_theme_color_override("font_color", profile.text_color)
	add_theme_color_override("font_hover_color", profile.text_hover_color)
	add_theme_color_override("font_focus_color", profile.text_hover_color)
	add_theme_color_override("font_pressed_color", profile.text_pressed_color)
	add_theme_color_override("font_disabled_color", profile.text_disabled_color)
	var label_control: Control = get_label_control()
	if label_control != self:
		label_control.add_theme_color_override("font_color", profile.text_color)
