extends Node
## Central registry for all runtime-tunable parameters.
## Add a new param with one _reg_f() call in _register_all() — the menu picks it up automatically.
## Access: DP.f("key")  •  Persist: DP.save() / DP.load_saved()  •  Reset: DP.reset_all()

signal any_changed(key: String, value: Variant)

const SAVE_PATH := "user://debug_params.cfg"

var _params: Dictionary = {}


func _ready() -> void:
	_register_all()
	load_saved()


func _register_all() -> void:
	# ── Movement ──────────────────────────────────────────────────────────────
	_reg_f("Movement", "thrust_impulse",      18.0,  1.0,  200.0)
	_reg_f("Movement", "thrust_vertical",      2.0,  0.0,   20.0)
	_reg_f("Movement", "max_speed",           30.0,  2.0, 1000.0)
	_reg_f("Movement", "roll_damping",         0.15, 0.01,  0.99, 0.01)
	_reg_f("Movement", "jump_velocity",        4.5,  1.0,  200.0)
	_reg_f("Movement", "visual_turn_speed",   12.0,  1.0,  200.0)
	_reg_f("Movement", "bounce_restitution",   0.55, 0.0,    2.0, 0.05)
	_reg_f("Movement", "bounce_min_impact",    1.0,  0.0,   20.0)
	_reg_f("Movement", "kick_impulse",         8.0,  1.0,  50.0)
	_reg_f("Movement", "kick_interval",        0.25, 0.05,  1.0, 0.01)
	_reg_f("Movement", "kick_vertical",        0.0,  0.0,  15.0)
	_reg_f("Movement", "kick_force_var",       0.2,  0.0,   1.0, 0.01)
	_reg_f("Movement", "kick_spread",         10.0,  0.0,  45.0, 1.0)
	_reg_f("Movement", "charge_kick_mult",     2.0,  1.0,   5.0)
	# ── Dust speed thresholds ─────────────────────────────────────────────────
	_reg_f("Dust",     "dust_walk_spd",       1.5,  0.1,  10.0)
	_reg_f("Dust",     "dust_charge_spd",     7.0,  1.0,  20.0)
	# ── Dust ──────────────────────────────────────────────────────────────────
	_reg_f("Dust",     "dust_lifetime",       0.9,  0.1,   4.0)
	_reg_f("Dust",     "dust_explosiveness",  0.35, 0.0,   1.0)
	_reg_f("Dust",     "dust_spread",        45.0,  5.0,  90.0)
	_reg_f("Dust",     "dust_gravity_y",     -1.5,-15.0,   0.0)
	_reg_f("Dust",     "walk_scale_min",      0.35, 0.05,  3.0)
	_reg_f("Dust",     "walk_scale_max",      0.65, 0.05,  3.0)
	_reg_f("Dust",     "walk_vel_min",        0.8,  0.1,  12.0)
	_reg_f("Dust",     "walk_vel_max",        1.8,  0.1,  12.0)
	_reg_f("Dust",     "charge_scale_min",    1.0,  0.05,  6.0)
	_reg_f("Dust",     "charge_scale_max",    2.2,  0.05,  6.0)
	_reg_f("Dust",     "charge_vel_min",      2.8,  0.1,  20.0)
	_reg_f("Dust",     "charge_vel_max",      5.5,  0.1,  20.0)
	# ── Tail ──────────────────────────────────────────────────────────────────
	_reg_f("Tail",     "tail_gravity",        9.8,   0.0, 30.0)
	_reg_f("Tail",     "tail_damping",        0.08,  0.0,  0.5, 0.01)
	_reg_f("Tail",     "tail_body_radius",    0.35,  0.05, 2.0, 0.05)
	_reg_f("Tail",     "tail_wag_freq",       4.0,   0.5, 15.0)
	_reg_f("Tail",     "tail_wag_idle",       0.3,   0.0,  2.0, 0.05)
	# ── Body ──────────────────────────────────────────────────────────────────
	_reg_f("Body",     "body_tilt_fwd",       0.015, 0.0,   0.1, 0.001)
	_reg_f("Body",     "body_tilt_side",      0.01,  0.0,   0.1, 0.001)
	_reg_f("Body",     "aerial_pitch_scale",  0.6,   0.0,   2.0)
	_reg_f("Body",     "aerial_pitch_speed",  5.0,   0.5,  20.0)
	# ── Legs ──────────────────────────────────────────────────────────────────
	_reg_f("Legs",     "leg_step_threshold",  0.15,  0.01,  1.0, 0.01)
	_reg_f("Legs",     "leg_step_duration",   0.12,  0.02,  0.5, 0.01)
	_reg_f("Legs",     "leg_step_height",     0.15,  0.0,   0.5)
	_reg_f("Legs",     "leg_step_overshoot",  0.3,   0.0,   1.0)
	_reg_f("Legs",     "gait_freq",           1.5,   0.1,   5.0)
	_reg_f("Legs",     "launch_hold_time",    0.15,  0.0,   1.0, 0.01)
	_reg_f("Legs",     "ragdoll_wobble_amp",  0.15,  0.0,   1.0)
	_reg_f("Legs",     "tuck_apex_threshold", 0.5,  -5.0,   5.0)
	_reg_f("Legs",     "jump_tuck_speed",     8.0,   1.0,  30.0)
	_reg_f("Legs",     "jump_tuck_up",        0.2,   0.0,   1.0)
	_reg_f("Legs",     "jump_tuck_fwd",      -0.1,  -0.5,   0.5)
	_reg_f("Legs",     "jump_land_threshold", 1.5,   0.5,   5.0)
	# ── Camera ────────────────────────────────────────────────────────────────
	_reg_f("Camera",   "cam_lerp_power",      6.0,   0.5,  30.0)
	_reg_f("Camera",   "cam_sensitivity",     0.005, 0.001, 0.05, 0.001)
	_reg_f("Camera",   "cam_min_zoom",        3.5,   0.5,  20.0)
	_reg_f("Camera",   "cam_max_zoom",       12.0,   2.0,  50.0)
	_reg_f("Camera",   "cam_spring_length",   7.0,   1.0,  30.0)
	_reg_f("Camera",   "cam_min_vert_angle", -60.0, -90.0,   0.0,  1.0)
	_reg_f("Camera",   "cam_max_vert_angle",  45.0,   0.0,  90.0,  1.0)
	# ── Matador ───────────────────────────────────────────────────────────────
	_reg_f("Matador",  "mat_spawn_count",     1.0,   1.0,  30.0,  1.0)
	_reg_f("Matador",  "mat_walk_speed",      2.5,   0.5,  10.0)
	_reg_f("Matador",  "mat_wander_radius",  22.0,   5.0,  50.0)
	_reg_f("Matador",  "mat_idle_min",        0.5,   0.0,   5.0, 0.1)
	_reg_f("Matador",  "mat_idle_max",        2.5,   0.5,  10.0, 0.1)
	_reg_f("Matador",  "mat_hit_threshold",   4.0,   1.0,  30.0)
	_reg_f("Matador",  "mat_ragdoll_impulse", 4.0,   0.5,  20.0)
	_reg_f("Matador",  "mat_flee_range",      10.0,  2.0,  40.0)
	_reg_f("Matador",  "mat_flee_speed",       4.0,  0.5,  12.0)
	_reg_f("Matador",  "mat_charge_speed",     6.0,  1.0,  30.0)
	_reg_f("Matador",  "mat_brace_range",      8.0,  2.0,  20.0)
	_reg_f("Matador",  "mat_brace_duration",   0.4,  0.05,  1.5, 0.05)
	_reg_f("Matador",  "mat_commit_dist",      2.5,  0.5,   6.0, 0.1)
	_reg_f("Matador",  "mat_step_speed",       9.0,  2.0,  20.0)
	_reg_f("Matador",  "mat_step_duration",    0.35, 0.1,   1.0, 0.05)
	_reg_f("Matador",  "mat_dodge_cooldown",   3.0,  0.5,   8.0, 0.25)
	# ── Debug ─────────────────────────────────────────────────────────────────
	_reg_b("Debug",    "show_collisions",    false)


func _reg_f(section: String, key: String, default: float,
		min_val: float, max_val: float, step: float = 0.05) -> void:
	_params[key] = {
		"value":   default,
		"default": default,
		"section": section,
		"type":    TYPE_FLOAT,
		"min":     min_val,
		"max":     max_val,
		"step":    step,
	}


func _reg_b(section: String, key: String, default: bool) -> void:
	_params[key] = {
		"value":   default,
		"default": default,
		"section": section,
		"type":    TYPE_BOOL,
	}


## Read a float param. Panics on unknown key — intentional: typos should be loud.
func f(key: String) -> float:
	return _params[key]["value"] as float


## Read a bool param.
func b(key: String) -> bool:
	return _params[key]["value"] as bool


func set_value(key: String, value: Variant) -> void:
	if not _params.has(key):
		return
	_params[key]["value"] = value
	any_changed.emit(key, value)


## Returns the full metadata dict — used by DebugMenu to build UI.
func get_all() -> Dictionary:
	return _params


func save() -> void:
	var cfg := ConfigFile.new()
	for key: String in _params:
		var p: Dictionary = _params[key]
		cfg.set_value(p["section"], key, p["value"])
	cfg.save(SAVE_PATH)


func load_saved() -> void:
	var cfg := ConfigFile.new()
	if cfg.load(SAVE_PATH) != OK:
		return
	for section: String in cfg.get_sections():
		for key: String in cfg.get_section_keys(section):
			if not _params.has(key):
				continue
			var p: Dictionary = _params[key]
			var val: Variant  = cfg.get_value(section, key)
			if p["type"] == TYPE_FLOAT:
				val = clampf(val as float, p["min"] as float, p["max"] as float)
			_params[key]["value"] = val


func reset_all() -> void:
	for key: String in _params:
		_params[key]["value"] = _params[key]["default"]
	any_changed.emit("__all__", null)
