extends Control

# === User-tweakable exports ===
@export var music_bus := "MusicBus"
@export var num_points := 128                   # base samples taken from analyzer
@export var samples_per_segment := 3            # higher -> smoother interpolation
@export var line_width := 3.0
@export var line_color := Color(0.2, 0.8, 1.0)
@export var fill_color := Color(0.2, 0.8, 1.0, 0.18)
@export var base_amplitude_scale := 0.8         # baseline (fraction of Control height)
@export var auto_scale := true                  # automatically adjust scale to avoid clipping
@export var auto_target_fraction := 0.85        # fraction of half-height we want peak to occupy
@export var auto_max_multiplier := 3.0
@export var scale_smoothness := 0.08            # smoothing for auto scale changes
@export var value_smoothness := 0.18            # smoothing for spectrum magnitudes
@export var spectrum_gain := 5.0                # multiplier on analyzer magnitude
@export var baseline_smoothness := 0.05         # smoothing for baseline centering

# === Internal ===
@onready var analyzer = AudioServer.get_bus_effect_instance(
	AudioServer.get_bus_index(music_bus), 0
)

var values: Array = []
var current_amplitude_scale: float = base_amplitude_scale
var baseline: float = 0.0

func _ready() -> void:
	values.resize(num_points)
	for i in range(num_points):
		values[i] = 0.0

func _process(delta: float) -> void:
	if not analyzer:
		return

	# --- sample spectrum and smooth values ---
	for i in range(num_points):
		var freq_low: float = lerp(20.0, 20000.0, float(i) / num_points)
		var freq_high: float = lerp(20.0, 20000.0, float(i + 1) / num_points)
		var mag_v: float = analyzer.get_magnitude_for_frequency_range(freq_low, freq_high).length() * spectrum_gain
		values[i] = lerp(values[i], mag_v, value_smoothness)

	# --- update auto scale (smoothed) ---
	_update_auto_scale()

	# --- update dynamic baseline for centering ---
	_update_baseline()

	queue_redraw()


func _update_auto_scale() -> void:
	if not auto_scale:
		current_amplitude_scale = lerp(current_amplitude_scale, base_amplitude_scale, scale_smoothness)
		return

	var peak: float = 0.0
	for v in values:
		if v > peak:
			peak = v

	var eps: float = 1e-6
	var half_h: float = size.y * 0.5
	var peak_pixels: float = peak * size.y * base_amplitude_scale
	var target_pixels: float = half_h * auto_target_fraction

	var required_mul: float = (target_pixels / (peak_pixels + eps))
	required_mul = clamp(required_mul, 1.0 / auto_max_multiplier, auto_max_multiplier)

	var new_scale: float = base_amplitude_scale * required_mul
	current_amplitude_scale = lerp(current_amplitude_scale, new_scale, scale_smoothness)


func _update_baseline() -> void:
	var avg_mag: float = 0.0
	for v in values:
		avg_mag += v
	avg_mag /= num_points
	baseline = lerp(baseline, avg_mag, baseline_smoothness)


# --- Catmull-Rom interpolation for smooth curves ---
func _catmull_rom_interpolate(raw_points: PackedVector2Array, samples_per_segment: int) -> PackedVector2Array:
	var n: int = raw_points.size()
	if n < 2:
		return raw_points.duplicate()

	var out := PackedVector2Array()
	for i in range(n - 1):
		var p0: Vector2 = raw_points[clamp(i - 1, 0, n - 1)]
		var p1: Vector2 = raw_points[i]
		var p2: Vector2 = raw_points[i + 1]
		var p3: Vector2 = raw_points[clamp(i + 2, 0, n - 1)]

		for s in range(samples_per_segment):
			var t: float = float(s) / samples_per_segment
			var t2: float = t * t
			var t3: float = t2 * t

			var x: float = 0.5 * ( (2.0 * p1.x) +
				(-p0.x + p2.x) * t +
				(2.0*p0.x - 5.0*p1.x + 4.0*p2.x - p3.x) * t2 +
				(-p0.x + 3.0*p1.x - 3.0*p2.x + p3.x) * t3 )
			var y: float = 0.5 * ( (2.0 * p1.y) +
				(-p0.y + p2.y) * t +
				(2.0*p0.y - 5.0*p1.y + 4.0*p2.y - p3.y) * t2 +
				(-p0.y + 3.0*p1.y - 3.0*p2.y + p3.y) * t3 )
			out.append(Vector2(x, y))
	out.append(raw_points[n - 1])
	return out


func _draw() -> void:
	if values.is_empty():
		return

	var mid_y: float = size.y * 0.5
	var step_x: float = size.x / float(num_points - 1)

	# raw top points (before interpolation)
	var raw := PackedVector2Array()
	for i in range(num_points):
		var x: float = i * step_x
		var y: float = mid_y - (values[i] - baseline) * size.y * current_amplitude_scale
		raw.append(Vector2(x, y))

	# smooth the curve
	var top_points: PackedVector2Array = _catmull_rom_interpolate(raw, samples_per_segment)

	# mirrored bottom points (reverse order)
	var bottom_points: PackedVector2Array = PackedVector2Array()
	for i in range(top_points.size() - 1, -1, -1):
		var p: Vector2 = top_points[i]
		bottom_points.append(Vector2(p.x, 2.0 * mid_y - p.y))

	# build filled polygon
	var poly: PackedVector2Array = PackedVector2Array()
	poly.append(Vector2(0, mid_y))
	for p in top_points:
		poly.append(p)
	poly.append(Vector2(size.x, mid_y))
	for p in bottom_points:
		poly.append(p)

	# draw filled area
	draw_polygon(poly, [fill_color])

	# draw top and bottom outlines
	if top_points.size() >= 2:
		draw_polyline(top_points, line_color, line_width, true)
		draw_polyline(bottom_points, line_color, line_width, true)
