extends Control

const WS_URL := "ws://localhost:8081/ws"
const MENU_SCENE := "res://scenes/menu.tscn"
const STREAM_INTERVAL_MS := 16
const NEXT_ROUND_DELAY_SEC := 2.5
const DEFAULT_DIGIT_INTENSITY := 180

enum Phase { IDLE, WAITING, PLAYING, WON }

@onready var status_label: Label = $UiLayer/TopBar/StatusLabel
@onready var target_label: Label = $UiLayer/Center/TargetLabel
@onready var subtitle_label: Label = $UiLayer/Center/SubtitleLabel
@onready var timer_label: Label = $UiLayer/Center/TimerLabel
@onready var hint_label: Label = $UiLayer/HintLabel
@onready var back_button: Button = $UiLayer/TopBar/BackButton
@onready var intensity_slider: HSlider = $UiLayer/SettingsPanel/IntensitySlider
@onready var intensity_value_label: Label = $UiLayer/SettingsPanel/IntensityValueLabel

var _socket := WebSocketPeer.new()
var _connected := false
var _phase := Phase.IDLE
var _pod_ids: PackedInt32Array = PackedInt32Array()
var _pod_digits: Dictionary = {}
var _target_digit := -1
var _target_cid := -1
var _round_start_ms := 0
var _round_token := 0


func _ready() -> void:
	back_button.pressed.connect(_return_to_menu)
	intensity_slider.value = DEFAULT_DIGIT_INTENSITY
	intensity_slider.value_changed.connect(_on_intensity_changed)
	_on_intensity_changed(intensity_slider.value)
	_set_phase(Phase.IDLE, "Verbinde…")
	_connect_ws()


func _exit_tree() -> void:
	_cleanup_streams()


func _return_to_menu() -> void:
	_cleanup_streams()
	get_tree().change_scene_to_file(MENU_SCENE)


func _connect_ws() -> void:
	_connected = false
	var err := _socket.connect_to_url(WS_URL)
	if err != OK:
		_set_phase(Phase.IDLE, "Verbindung fehlgeschlagen: %s" % error_string(err))


func _send(payload: Dictionary) -> void:
	if _socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
		return
	_socket.send_text(JSON.stringify(payload))


func _cleanup_streams() -> void:
	if _socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
		return
	_send({"type": "set_stream", "enable": false, "interval_ms": STREAM_INTERVAL_MS})
	for cid in _pod_ids:
		_send({
			"type": "set_tap_notify",
			"client_id": cid,
			"single": false,
			"double_tap": false,
			"triple": false,
		})
		_send_led_clear(cid)


func _physics_process(_delta: float) -> void:
	_socket.poll()
	var state := _socket.get_ready_state()

	if state == WebSocketPeer.STATE_OPEN:
		while _socket.get_available_packet_count() > 0:
			_handle_message(_socket.get_packet().get_string_from_utf8())
		if _connected:
			status_label.text = "Verbunden · %s" % WS_URL
		if _phase == Phase.PLAYING:
			_update_live_timer()
	elif state == WebSocketPeer.STATE_CONNECTING:
		status_label.text = "Verbinde…"
	elif state == WebSocketPeer.STATE_CLOSING or state == WebSocketPeer.STATE_CLOSED:
		_connected = false
		_pod_ids = PackedInt32Array()
		_set_phase(Phase.IDLE, "Getrennt – erneuter Verbindungsversuch…")
		if state == WebSocketPeer.STATE_CLOSED:
			_connect_ws()


func _handle_message(text: String) -> void:
	var data = JSON.parse_string(text)
	if typeof(data) != TYPE_DICTIONARY:
		return

	match data.get("type", ""):
		"hello":
			_connected = true
			_send({"type": "list_clients"})
		"client_list":
			_on_client_list(data)
		"input":
			_on_input(data)


func _on_client_list(data: Dictionary) -> void:
	if not data.get("success", false):
		_set_phase(Phase.IDLE, "Client-Liste fehlgeschlagen: %s" % data.get("error", "?"))
		return

	_pod_ids = PackedInt32Array()
	for entry in data.get("clients", []):
		if typeof(entry) != TYPE_DICTIONARY:
			continue
		if not entry.get("available", false):
			continue
		var cid := int(entry.get("id", 0))
		if cid > 0:
			_pod_ids.append(cid)

	_pod_ids.sort()

	if _pod_ids.size() < 2:
		_set_phase(Phase.WAITING, "Mindestens 2 online Pods nötig (aktuell: %d)" % _pod_ids.size())
		target_label.text = "—"
		timer_label.text = ""
		return

	_start_round()


func _start_round() -> void:
	_round_token += 1
	_pod_digits.clear()

	var pool: Array = []
	for d in range(10):
		pool.append(d)
	pool.shuffle()

	for i in _pod_ids.size():
		_pod_digits[_pod_ids[i]] = int(pool[i])

	var pick_idx := randi() % _pod_ids.size()
	_target_cid = _pod_ids[pick_idx]
	_target_digit = int(_pod_digits[_target_cid])

	for cid in _pod_ids:
		_send_led_digit(cid, int(_pod_digits[cid]))

	_enable_input_stream()
	_round_start_ms = Time.get_ticks_msec()
	_set_phase(Phase.PLAYING, "%d Pods bereit" % _pod_ids.size())
	target_label.text = str(_target_digit)
	subtitle_label.text = "Drücke den Pod mit dieser Zahl"
	timer_label.text = "0,00 s"
	hint_label.text = ""


func _enable_input_stream() -> void:
	for cid in _pod_ids:
		_send({
			"type": "set_tap_notify",
			"client_id": cid,
			"single": true,
			"double_tap": false,
			"triple": false,
		})
	_send({"type": "set_stream", "enable": true, "interval_ms": STREAM_INTERVAL_MS})


func _on_input(data: Dictionary) -> void:
	if _phase != Phase.PLAYING or not data.get("success", false):
		return

	for client in data.get("clients", []):
		if typeof(client) != TYPE_DICTIONARY:
			continue
		if str(client.get("tap_kind", "none")) == "none":
			continue
		var cid := int(client.get("client_id", 0))
		if not _pod_digits.has(cid):
			continue
		if cid == _target_cid:
			_on_correct_tap()
		else:
			hint_label.text = "Falsch! Pod #%d hat %d — versuch es nochmal." % [cid, int(_pod_digits[cid])]
			_send_led_blink(cid, Color(1.0, 0.2, 0.2))
			var wrong_digit := int(_pod_digits[cid])
			get_tree().create_timer(0.5).timeout.connect(
				func() -> void:
					if _phase == Phase.PLAYING and _pod_digits.has(cid):
						_send_led_digit(cid, wrong_digit),
				CONNECT_ONE_SHOT
			)


func _on_correct_tap() -> void:
	var elapsed_sec := (Time.get_ticks_msec() - _round_start_ms) / 1000.0
	_set_phase(Phase.WON, "%d Pods" % _pod_ids.size())
	timer_label.text = "%.2f s" % elapsed_sec
	subtitle_label.text = "Richtig!"
	hint_label.text = "Nächste Runde in %.0f s…" % NEXT_ROUND_DELAY_SEC
	_send_led_blink(_target_cid, Color(0.2, 1.0, 0.35))

	var token := _round_token
	get_tree().create_timer(NEXT_ROUND_DELAY_SEC).timeout.connect(
		func() -> void:
			if _round_token != token or not is_inside_tree():
				return
			if _pod_ids.size() >= 2:
				_start_round(),
		CONNECT_ONE_SHOT
	)


func _update_live_timer() -> void:
	var elapsed_sec := (Time.get_ticks_msec() - _round_start_ms) / 1000.0
	timer_label.text = "%.2f s" % elapsed_sec


func _set_phase(phase: Phase, status: String) -> void:
	_phase = phase
	status_label.text = status
	if phase == Phase.WAITING or phase == Phase.IDLE:
		subtitle_label.text = "Warte auf Pods…"
		hint_label.text = ""


func _digit_intensity() -> int:
	return int(intensity_slider.value)


func _on_intensity_changed(_value: float) -> void:
	intensity_value_label.text = str(_digit_intensity())
	if _pod_digits.is_empty():
		return
	for cid in _pod_digits:
		_send_led_digit(cid, int(_pod_digits[cid]))


func _send_led_digit(cid: int, digit: int) -> void:
	_send({
		"type": "set_led_ring",
		"client_id": cid,
		"mode": "digit",
		"digit": digit,
		"r": 40,
		"g": 220,
		"b": 255,
		"intensity": _digit_intensity(),
	})


func _send_led_clear(cid: int) -> void:
	_send({"type": "set_led_ring", "client_id": cid, "mode": "clear"})


func _send_led_blink(cid: int, color: Color) -> void:
	_send({
		"type": "set_led_ring",
		"client_id": cid,
		"mode": "blink",
		"blink_ms": 200,
		"blink_count": 2,
		"r": int(color.r * 255),
		"g": int(color.g * 255),
		"b": int(color.b * 255),
		"intensity": _digit_intensity(),
	})
