extends Control

signal calibration_finished(calibration: AccelCalibration)

enum Phase { WAITING, NEUTRAL, RIGHT, LEFT, DONE }

const TRAVEL_PX := 140.0
const NEUTRAL_DURATION := 3.5
const SWAY_DURATION := 5.0
const SAMPLE_START_FRACTION := 0.5
const CIRCLE_RADIUS := 44.0
const NEEDLE_LEN := 58.0

var _phase := Phase.WAITING
var _phase_time := 0.0
var _guide_offset := Vector2.ZERO
var _instruction := ""
var _calibration := AccelCalibration.new()
var _connection_notified := false
var _context_label := ""
var _last_accel := Vector3i.ZERO

@onready var _hint_label: Label = $HintLabel
@onready var _start_button: Button = $StartButton


func _ready() -> void:
	set_anchors_preset(Control.PRESET_FULL_RECT)
	mouse_filter = Control.MOUSE_FILTER_PASS
	_start_button.pressed.connect(_on_start_pressed)
	_show_waiting()


func set_context_label(text: String) -> void:
	_context_label = text
	if _phase == Phase.WAITING:
		_show_waiting()


func start() -> void:
	_calibration.reset()
	_connection_notified = false
	_phase = Phase.WAITING
	_phase_time = 0.0
	_guide_offset = Vector2.ZERO
	_last_accel = Vector3i.ZERO
	visible = true
	_start_button.visible = true
	_show_waiting()
	queue_redraw()


func is_active() -> bool:
	return _phase != Phase.DONE


func feed_accel(accel: Vector3i) -> void:
	_last_accel = accel
	if not _should_record():
		return
	match _phase:
		Phase.NEUTRAL:
			_calibration.record_neutral(accel)
		Phase.RIGHT:
			_calibration.record_right(accel)
		Phase.LEFT:
			_calibration.record_left(accel)


func get_live_projection() -> float:
	if _calibration.uses_vector:
		return _calibration.project(_last_accel) - _calibration.center
	return 0.0


func notify_connected() -> void:
	if _phase != Phase.WAITING or _connection_notified:
		return
	_hint_label.text = "Verbunden – tippe „Kalibrierung starten“ oder warte…"
	begin_calibration()


func begin_calibration() -> void:
	if _phase == Phase.NEUTRAL or _phase == Phase.RIGHT or _phase == Phase.LEFT:
		return
	_connection_notified = true
	_start_button.visible = false
	_begin_phase(Phase.NEUTRAL)


func _on_start_pressed() -> void:
	begin_calibration()


func _process(delta: float) -> void:
	if _phase == Phase.WAITING or _phase == Phase.DONE:
		return

	_phase_time += delta
	var duration := _phase_duration()
	var t := clampf(_phase_time / duration, 0.0, 1.0)
	var ease := t * t * (3.0 - 2.0 * t)

	match _phase:
		Phase.NEUTRAL:
			_guide_offset = Vector2.ZERO
			_instruction = _phase_instruction(
				"Halte den Zylinder senkrecht und ruhig\n(mittlere Position – nicht schwanken)"
			)
			if _phase_time >= duration:
				_begin_phase(Phase.RIGHT)
		Phase.RIGHT:
			_guide_offset.x = TRAVEL_PX * ease
			_instruction = _phase_instruction(
				"Schwank den Pod langsam nach rechts ↷\nWie ein Pendel – Spitze kippt nach rechts"
			)
			if _phase_time >= duration:
				_begin_phase(Phase.LEFT)
		Phase.LEFT:
			_guide_offset.x = -TRAVEL_PX * ease
			_instruction = _phase_instruction(
				"Schwank den Pod langsam nach links ↶\nWie ein Pendel – Spitze kippt nach links"
			)
			if _phase_time >= duration:
				_finish()

	var record_hint := ""
	if _phase != Phase.NEUTRAL and _phase_time < duration * SAMPLE_START_FRACTION:
		record_hint = "\n\n… halte gleich die Endposition"
	elif _phase != Phase.WAITING and _phase != Phase.DONE:
		record_hint = "\n\n✓ Aufnahme läuft"

	_hint_label.text = _instruction + record_hint
	queue_redraw()


func _phase_duration() -> float:
	match _phase:
		Phase.NEUTRAL:
			return NEUTRAL_DURATION
	return SWAY_DURATION


func _should_record() -> bool:
	if _phase == Phase.WAITING or _phase == Phase.DONE:
		return false
	return _phase_time >= _phase_duration() * SAMPLE_START_FRACTION


func _begin_phase(phase: Phase) -> void:
	_phase = phase
	_phase_time = 0.0
	_guide_offset = Vector2.ZERO
	match phase:
		Phase.NEUTRAL:
			_instruction = _phase_instruction(
				"Halte den Zylinder senkrecht und ruhig\n(mittlere Position – nicht schwanken)"
			)
		Phase.RIGHT:
			_instruction = _phase_instruction(
				"Schwank den Pod langsam nach rechts ↷\nWie ein Pendel – Spitze kippt nach rechts"
			)
		Phase.LEFT:
			_instruction = _phase_instruction(
				"Schwank den Pod langsam nach links ↶\nWie ein Pendel – Spitze kippt nach links"
			)
	_hint_label.text = _instruction
	queue_redraw()


func _finish() -> void:
	_phase = Phase.DONE
	visible = false
	if _calibration.analyze():
		calibration_finished.emit(_calibration.duplicate())
	else:
		_hint_label.text = (
			"Kalibrierung fehlgeschlagen.\n"
			+ "Schwank weiter links/rechts oder halte die Neutralposition länger still."
		)
		visible = true
		_start_button.visible = true
		_show_waiting()


func _phase_instruction(base: String) -> String:
	if _context_label == "":
		return base
	return "%s\n\n%s" % [_context_label, base]


func _show_waiting() -> void:
	_instruction = "Warte auf Verbindung zu localhost:8081…"
	var prefix := "%s\n\n" % _context_label if _context_label != "" else ""
	_hint_label.text = prefix + _instruction + "\n(Oder „Kalibrierung starten“ ohne Verbindung)"


func _screen_center() -> Vector2:
	return get_viewport_rect().size * 0.5


func _draw() -> void:
	if _phase == Phase.DONE:
		return

	var pos := _screen_center() + _guide_offset
	var center := _screen_center()

	if _phase == Phase.NEUTRAL:
		var pulse := 0.85 + 0.15 * sin(_phase_time * 4.0)
		draw_circle(center, CIRCLE_RADIUS * pulse, Color(0.35, 0.85, 0.55, 0.18))
		draw_arc(center, CIRCLE_RADIUS * pulse, 0.0, TAU, 64, Color(0.45, 0.95, 0.65), 2.5)
		draw_line(center + Vector2(-18, 0), center + Vector2(18, 0), Color(1, 1, 1, 0.35), 2.0)
		return

	draw_circle(pos, CIRCLE_RADIUS, Color(0.25, 0.72, 0.95, 0.2))
	draw_arc(pos, CIRCLE_RADIUS, 0.0, TAU, 64, Color(0.35, 0.82, 1.0), 2.5)

	var needle_tip := pos + Vector2(0.0, -NEEDLE_LEN)
	draw_line(pos, needle_tip, Color(1.0, 1.0, 1.0, 0.95), 3.0)
	draw_circle(needle_tip, 5.0, Color(1.0, 1.0, 1.0))

	if _phase == Phase.RIGHT:
		draw_line(center, center + Vector2(TRAVEL_PX, 0.0), Color(1.0, 1.0, 1.0, 0.12), 1.0)
	elif _phase == Phase.LEFT:
		draw_line(center, center + Vector2(-TRAVEL_PX, 0.0), Color(1.0, 1.0, 1.0, 0.12), 1.0)
