## 游戏设计器通用 UI 辅助（静态方法 + 共享常量，各标签页复用）
@tool
extends RefCounted

## 元素词表（spell_tab 复选框 / resonance_tab 元素下拉共用）；label 双语显示，tag 存储，index 对齐
const ELEM_LABELS := ["火 Fire", "冰 Ice", "雷 Lightning", "水 Water", "毒 Poison"]
const ELEM_TAGS   := ["tag:fire", "tag:ice", "tag:lightning", "tag:water", "tag:poison"]

static func spin(min_v: float, max_v: float, step: float, val: float) -> SpinBox:
	var s := SpinBox.new()
	s.min_value = min_v; s.max_value = max_v; s.step = step
	s.value = val
	s.custom_minimum_size = Vector2(80, 0)
	s.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	return s

static func line(text: String, placeholder: String = "") -> LineEdit:
	var le := LineEdit.new()
	le.text = text
	le.placeholder_text = placeholder
	le.custom_minimum_size = Vector2(120, 0)
	le.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	return le

static func opt(items: Array, selected: int) -> OptionButton:
	var o := OptionButton.new()
	for it in items:
		o.add_item(str(it))
	o.selected = clampi(selected, 0, items.size() - 1)
	o.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	return o

static func header(text: String) -> Label:
	var l := Label.new()
	l.text = text
	l.add_theme_font_size_override("font_size", 15)
	l.modulate = Color(1.0, 0.85, 0.4)
	return l

static func cell_label(text: String) -> Label:
	var l := Label.new()
	l.text = text
	l.custom_minimum_size = Vector2(70, 0)
	return l

static func btn(text: String, cb: Callable) -> Button:
	var b := Button.new()
	b.text = text
	b.pressed.connect(cb)
	return b

static func status_label() -> Label:
	var l := Label.new()
	l.add_theme_font_size_override("font_size", 11)
	l.modulate = Color(0.6, 0.9, 0.6)
	return l

static func set_status(lbl: Label, msg: String, is_err: bool = false) -> void:
	if lbl:
		lbl.text = msg
		lbl.modulate = Color(1.0, 0.5, 0.5) if is_err else Color(0.6, 0.9, 0.6)

static func load_json(path: String):
	if not FileAccess.file_exists(path):
		return null
	return JSON.parse_string(FileAccess.get_file_as_string(path))

static func save_json(path: String, data) -> bool:
	DirAccess.make_dir_recursive_absolute("res://data")
	var f := FileAccess.open(path, FileAccess.WRITE)
	if f:
		f.store_string(JSON.stringify(data, "  "))
		f.close()
		return true
	return false
