extends CanvasLayer
## The settings window (Settings autoload, settings.gd): parchment window, brown wood tab column on
## the left (Graphics / Lighting & Color / Audio / Interface / Controls / Language), a scrolling page of
## cards with switches, sliders (live value) and dropdowns, footer with Reset tab / Reset all / Close.
## Every widget writes Settings at once (live, saved shortly after). Widgets + theme reuse auto_ui.gd
## (its `hud` is this node: the same colour constants and builder names as hud.gd).
## Esc or Alt+O closes it; "Peek" hides the window while held (see the scene behind the sliders).

const HudScript = preload("res://scripts/hud.gd")
const AutoUi = preload("res://scripts/auto_ui.gd")
const UiSkin = preload("res://scripts/ui/ui_skin.gd")

const PARCH := HudScript.PARCH
const FRAME := HudScript.FRAME
const PLAQUE := HudScript.PLAQUE
const INK := HudScript.INK
const INK_SOFT := HudScript.INK_SOFT
const KEY := HudScript.KEY
const GOOD := HudScript.GOOD
const BAD := HudScript.BAD
const CREAM := HudScript.CREAM
const BTN_GREEN := HudScript.BTN_GREEN
const BTN_GOLD := HudScript.BTN_GOLD
const BTN_RED := HudScript.BTN_RED

const TABS := [["graphics", "Graphics"], ["lighting", "Lighting & Color"], ["audio", "Audio"], ["interface", "Interface"],
	["controls", "Controls"], ["language", "Language"]]
const TAB_W := 176.0
const PAGE_W := 700.0
const FS := 18
const FS_SMALL := 15
const LABEL_W := 270.0

var settings = null
var tab := "graphics"
var root: Control
var panel: PanelContainer
var page: VBoxContainer
var scroll: ScrollContainer
var foot_lbl: Label
var preset_lbl: Label
var tone_lbl: Label
var conflict_lbl: Label
var _au
var _tab_btns: Dictionary = {}
var _rebuild_queued := false
var _capture_btn: Button = null


func _ready() -> void:
	layer = 70
	_au = AutoUi.new()
	_au.hud = self
	add_child(_au)
	_au._make_theme()
	root = Control.new()
	root.set_anchors_preset(Control.PRESET_FULL_RECT)
	root.mouse_filter = Control.MOUSE_FILTER_IGNORE
	root.theme = _base_theme()
	root.theme.merge_with(_au._theme)
	root.visible = false
	add_child(root)
	_build()
	settings.changed.connect(_on_changed)
	get_viewport().size_changed.connect(_fit)


func is_open() -> bool:
	return root != null and root.visible


func open(t: String = "") -> void:
	if t != "":
		tab = t
	root.visible = true
	show_tab(tab)
	_fit()
	# again once the new page has its size
	_fit.call_deferred()


func close() -> void:
	_end_capture()
	root.visible = false


# ---------------------------------------------------------------- builders (hud.gd look)

func _flat(bg: Color, border: Color = Color(0, 0, 0, 0), bw: int = 0, radius: int = 6) -> StyleBoxFlat:
	var sb := StyleBoxFlat.new()
	sb.bg_color = bg
	sb.border_color = border
	sb.set_border_width_all(bw)
	sb.set_corner_radius_all(radius)
	sb.set_content_margin_all(8)
	return sb


func _btn_styles(base: Color) -> Array:
	var n := _flat(base, base.darkened(0.45), 2, 7)
	n.content_margin_left = 12
	n.content_margin_right = 12
	n.content_margin_top = 3
	n.content_margin_bottom = 3
	var h: StyleBoxFlat = n.duplicate()
	h.bg_color = base.lightened(0.15)
	var p: StyleBoxFlat = n.duplicate()
	p.bg_color = base.darkened(0.2)
	var d: StyleBoxFlat = n.duplicate()
	d.bg_color = Color(0.72, 0.66, 0.56)
	d.border_color = Color(0.55, 0.49, 0.4)
	return [n, h, p, d]


func _label(text: String = "", size: int = 0, color: Color = INK) -> Label:
	var l := Label.new()
	l.text = text
	if size > 0:
		l.add_theme_font_size_override("font_size", size)
	l.add_theme_color_override("font_color", color)
	return l


func _world_label(text: String, size: int, color: Color = Color(1, 1, 1)) -> Label:
	var l := _label(text, size, color)
	l.add_theme_color_override("font_outline_color", Color(0.05, 0.03, 0.02))
	l.add_theme_constant_override("outline_size", 4)
	return l


func _button(text: String, cb: Callable, disabled: bool = false, tip: String = "", kind: String = "ok") -> Button:
	var b := Button.new()
	b.text = text
	b.disabled = disabled
	b.tooltip_text = tip
	b.focus_mode = Control.FOCUS_NONE
	b.pressed.connect(cb)
	if kind != "ok":
		var st := _btn_styles(BTN_GOLD if kind == "alt" else BTN_RED)
		b.add_theme_stylebox_override("normal", st[0])
		b.add_theme_stylebox_override("hover", st[1])
		b.add_theme_stylebox_override("pressed", st[2])
		b.add_theme_stylebox_override("disabled", st[3])
	b.add_theme_font_size_override("font_size", FS)
	b.custom_minimum_size = Vector2(0, 38)
	return b


func _grow(c: Control) -> Control:
	c.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	return c


func _clear(c: Node) -> void:
	for ch in c.get_children():
		c.remove_child(ch)
		ch.queue_free()


func _base_theme() -> Theme:
	var th := Theme.new()
	var f := SystemFont.new()
	f.font_names = PackedStringArray(["Leelawadee UI", "Segoe UI", "Tahoma", "Arial"])
	th.default_font = f
	th.default_font_size = FS
	var pc := _flat(PARCH, FRAME, 3, 12)
	pc.shadow_color = Color(0.15, 0.08, 0.02, 0.4)
	pc.shadow_size = 10
	pc.shadow_offset = Vector2(0, 4)
	pc.set_content_margin_all(10)
	th.set_stylebox("panel", "PanelContainer", pc)
	var st := _btn_styles(BTN_GREEN)
	th.set_stylebox("normal", "Button", st[0])
	th.set_stylebox("hover", "Button", st[1])
	th.set_stylebox("pressed", "Button", st[2])
	th.set_stylebox("disabled", "Button", st[3])
	th.set_stylebox("focus", "Button", StyleBoxEmpty.new())
	for c in ["font_color", "font_hover_color", "font_pressed_color", "font_focus_color"]:
		th.set_color(c, "Button", Color(1, 1, 1))
	th.set_color("font_outline_color", "Button", Color(0.18, 0.12, 0.06))
	th.set_constant("outline_size", "Button", 3)
	th.set_color("font_color", "Label", INK)
	th.set_stylebox("panel", "TooltipPanel", _flat(PARCH, FRAME, 2, 6))
	th.set_color("font_color", "TooltipLabel", INK)
	# UI v3 (ui/ui_skin.gd): Kanit, the parchment page, glossy buttons, scroll bars
	UiSkin.skin_theme(th)
	return th


# ---------------------------------------------------------------- window

func _build() -> void:
	panel = PanelContainer.new()
	root.add_child(panel)
	var v := VBoxContainer.new()
	v.add_theme_constant_override("separation", 8)
	panel.add_child(v)
	# header plaque: title (drag handle), Peek, close
	var plaque := PanelContainer.new()
	var ps := _flat(PLAQUE, Color(0.72, 0.55, 0.3), 2, 8)
	ps.content_margin_top = 4
	ps.content_margin_bottom = 4
	ps.content_margin_left = 12
	plaque.add_theme_stylebox_override("panel", ps)
	v.add_child(plaque)
	var bar := HBoxContainer.new()
	bar.add_theme_constant_override("separation", 8)
	plaque.add_child(bar)
	var gear := _label("⚙", 22, Color(1, 0.85, 0.45))
	bar.add_child(gear)
	var t: Label = _grow(_label("Settings", 21, CREAM))
	t.mouse_filter = Control.MOUSE_FILTER_STOP
	t.mouse_default_cursor_shape = Control.CURSOR_MOVE
	t.gui_input.connect(_drag)
	bar.add_child(t)
	var peek: Button = _au.chip("Peek", func(): pass, "Hold to hide this window and look at the scene behind it.")
	peek.button_down.connect(func(): panel.modulate.a = 0.06)
	peek.button_up.connect(func(): panel.modulate.a = 1.0)
	bar.add_child(peek)
	var x := _button("x", close, false, "Close (Esc)", "danger")
	var xs := _flat(BTN_RED, Color(0.45, 0.2, 0.05), 2, 14)
	xs.content_margin_left = 9
	xs.content_margin_right = 9
	xs.content_margin_top = 0
	xs.content_margin_bottom = 1
	x.add_theme_stylebox_override("normal", xs)
	x.custom_minimum_size = Vector2(30, 30)
	bar.add_child(x)
	if UiSkin.v3():
		# UI v3: leather title pill with the medallion, round close button (ui/ui_skin.gd)
		var eps := StyleBoxEmpty.new()
		eps.content_margin_bottom = 2
		plaque.add_theme_stylebox_override("panel", eps)
		gear.visible = false
		UiSkin.style_title(t, 20)
		t.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
		t.custom_minimum_size = Vector2(260, 38)
		var sp := Control.new()
		sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
		bar.add_child(sp)
		bar.move_child(sp, t.get_index() + 1)
		UiSkin.style_close(x, 36.0)
		root.visibility_changed.connect(func():
			if root.visible:
				UiSkin.open_anim(panel))
	# tab column + page
	var body := HBoxContainer.new()
	body.add_theme_constant_override("separation", 10)
	v.add_child(body)
	var tabs := VBoxContainer.new()
	tabs.add_theme_constant_override("separation", 5)
	tabs.custom_minimum_size = Vector2(TAB_W, 0)
	body.add_child(tabs)
	for tt in TABS:
		var b := Button.new()
		b.text = str(tt[1])
		b.focus_mode = Control.FOCUS_NONE
		b.alignment = HORIZONTAL_ALIGNMENT_LEFT
		b.custom_minimum_size = Vector2(TAB_W, 46)
		b.add_theme_font_size_override("font_size", FS)
		b.pressed.connect(show_tab.bind(str(tt[0])))
		b.name = "Tab_" + str(tt[0])
		tabs.add_child(b)
		_tab_btns[str(tt[0])] = b
	var frame := PanelContainer.new()
	var fs: StyleBoxFlat = _flat(Color("#f7edd3"), Color(FRAME, 0.7), 2, 10)
	fs.set_content_margin_all(4)
	frame.add_theme_stylebox_override("panel", fs if not UiSkin.v3() else UiSkin.inset(5.0, 1))
	body.add_child(frame)
	scroll = ScrollContainer.new()
	scroll.custom_minimum_size = Vector2(PAGE_W, 520)
	scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
	frame.add_child(scroll)
	page = VBoxContainer.new()
	page.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	page.custom_minimum_size = Vector2(PAGE_W - 22, 0)
	page.add_theme_constant_override("separation", 10)
	scroll.add_child(page)
	_au.page = page
	# footer
	var ft := HBoxContainer.new()
	ft.add_theme_constant_override("separation", 8)
	v.add_child(ft)
	foot_lbl = _grow(_label("Changes apply at once and are saved.", FS_SMALL, INK_SOFT))
	foot_lbl.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
	ft.add_child(foot_lbl)
	ft.add_child(_button("Reset this tab", _reset_tab, false, "Put the options of this tab back to their defaults.", "alt"))
	ft.add_child(_button("Reset all", _reset_all, false, "Put every setting back to its default.", "danger"))
	ft.add_child(_button("Close", close, false, "Close (Esc)"))


func _drag(ev: InputEvent) -> void:
	if ev is InputEventMouseMotion and (ev.button_mask & MOUSE_BUTTON_MASK_LEFT):
		panel.position += ev.relative
		_clamp()


## Size to the view (UI scale / small windows): the page scrolls, the window stays on screen.
func _fit() -> void:
	if not is_inside_tree() or panel == null:
		return
	var vp: Vector2 = root.get_viewport_rect().size
	var h := clampf(vp.y - 210.0, 240.0, 560.0)
	scroll.custom_minimum_size = Vector2(PAGE_W, h)
	# shrink to the content (a PanelContainer never shrinks by itself), then centre
	panel.size = Vector2.ZERO
	var ms := panel.get_combined_minimum_size()
	panel.size = ms
	panel.position = ((vp - ms) * 0.5).max(Vector2(6, 6))
	_clamp()


func _clamp() -> void:
	var vp: Vector2 = root.get_viewport_rect().size
	panel.position = panel.position.clamp(Vector2.ZERO, (vp - panel.size).max(Vector2.ZERO))


func _reset_tab() -> void:
	settings.reset(tab)
	_note(tr("Defaults restored") + ": " + tr(_tab_title(tab)))


func _reset_all() -> void:
	settings.reset()
	_note(tr("Every setting is back to its default."))


func _note(text: String, col: Color = INK_SOFT) -> void:
	foot_lbl.text = text
	foot_lbl.add_theme_color_override("font_color", col)


func _tab_title(id: String) -> String:
	for tt in TABS:
		if tt[0] == id:
			return str(tt[1])
	return id


func _on_changed(key: String) -> void:
	if not is_open():
		return
	if key in ["gfx_preset", "tone", "reset", "keys", "language"]:
		if not _rebuild_queued:
			_rebuild_queued = true
			_rebuild.call_deferred()
	_update_labels()


func _rebuild() -> void:
	_rebuild_queued = false
	if is_open():
		var sv := scroll.scroll_vertical
		show_tab(tab)
		scroll.scroll_vertical = sv


func _update_labels() -> void:
	if is_instance_valid(preset_lbl):
		preset_lbl.text = tr("Current") + ": " + tr(str(settings.val("gfx_preset")).capitalize())
	if is_instance_valid(tone_lbl):
		tone_lbl.text = tr("Current") + ": " + tr(_tone_name(str(settings.val("tone"))))


func _tone_name(id: String) -> String:
	return {"vivid": "Vivid", "soft": "Soft pastel", "cinematic": "Cinematic", "evening": "Evening"}.get(id, "Custom")


# ---------------------------------------------------------------- widgets

func _name_label(text: String, tip: String, w: float = 0.0) -> Label:
	var l := _label(text, FS, INK)
	l.tooltip_text = tip
	l.mouse_filter = Control.MOUSE_FILTER_PASS
	if w > 0.0:
		l.custom_minimum_size = Vector2(w, 0)
	else:
		l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	return l


func note(parent: Control, text: String) -> Label:
	var l := _label(text, FS_SMALL, INK_SOFT)
	l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
	l.custom_minimum_size = Vector2(PAGE_W - 60, 0)
	parent.add_child(l)
	return l


func switch_row(parent: Control, key: String, text: String, tip: String) -> CheckButton:
	var r := HBoxContainer.new()
	r.add_theme_constant_override("separation", 8)
	parent.add_child(r)
	r.add_child(_name_label(text, tip))
	var sw := CheckButton.new()
	sw.name = "Opt_" + key
	sw.focus_mode = Control.FOCUS_NONE
	sw.button_pressed = settings.on(key)
	sw.tooltip_text = tip
	sw.toggled.connect(func(v: bool): settings.set_value(key, v))
	r.add_child(sw)
	return sw


## Slider for number `key`; the slider shows value x `mul` with `fmt` ("%d%%"), fmt "temp" = cool / warm.
func slider_row(parent: Control, key: String, text: String, tip: String, lo: float, hi: float, step: float, fmt: String,
		mul: float = 1.0) -> HSlider:
	var r := HBoxContainer.new()
	r.add_theme_constant_override("separation", 10)
	parent.add_child(r)
	r.add_child(_name_label(text, tip, LABEL_W))
	var s := HSlider.new()
	s.name = "Opt_" + key
	s.min_value = lo
	s.max_value = hi
	s.step = step
	s.value = settings.num(key) * mul
	s.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	s.size_flags_vertical = Control.SIZE_SHRINK_CENTER
	s.custom_minimum_size = Vector2(200, 30)
	s.focus_mode = Control.FOCUS_NONE
	s.tooltip_text = tip
	r.add_child(s)
	var val := _label(_fmt(fmt, s.value), FS, PLAQUE)
	val.custom_minimum_size = Vector2(96, 0)
	val.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	r.add_child(val)
	s.value_changed.connect(_on_slider.bind(key, val, fmt, mul))
	return s


func _on_slider(v: float, key: String, val: Label, fmt: String, mul: float) -> void:
	settings.set_value(key, v / mul)
	val.text = _fmt(fmt, v)


func _fmt(fmt: String, v: float) -> String:
	if fmt == "temp":
		if absf(v) < 0.5:
			return tr("Neutral")
		return (tr("Warm") if v > 0.0 else tr("Cool")) + " %d" % int(round(absf(v)))
	if fmt == "x":
		return "x%.2f" % v
	return fmt % v


## Dropdown for `key`; options = [[value, text], ...] (value int or String).
func option_row(parent: Control, key: String, text: String, tip: String, options: Array) -> OptionButton:
	var r := HBoxContainer.new()
	r.add_theme_constant_override("separation", 10)
	parent.add_child(r)
	r.add_child(_name_label(text, tip, LABEL_W))
	var ob := OptionButton.new()
	ob.name = "Opt_" + key
	ob.focus_mode = Control.FOCUS_NONE
	ob.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	ob.tooltip_text = tip
	ob.custom_minimum_size = Vector2(0, 36)
	var cur := str(settings.val(key))
	for i in options.size():
		ob.add_item(str(options[i][1]), i)
		ob.set_item_metadata(i, options[i][0])
		if str(options[i][0]) == cur:
			ob.select(i)
	ob.item_selected.connect(func(i: int): settings.set_value(key, ob.get_item_metadata(i)))
	r.add_child(ob)
	return ob


## Volume slider + an on / off switch in one row.
func volume_row(parent: Control, vkey: String, mkey: String, text: String, tip: String) -> void:
	var r := HBoxContainer.new()
	r.add_theme_constant_override("separation", 10)
	parent.add_child(r)
	r.add_child(_name_label(text, tip, 200.0))
	var s := HSlider.new()
	s.name = "Opt_" + vkey
	s.min_value = 0
	s.max_value = 100
	s.step = 5
	s.value = settings.num(vkey) * 100.0
	s.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	s.size_flags_vertical = Control.SIZE_SHRINK_CENTER
	s.custom_minimum_size = Vector2(200, 30)
	s.focus_mode = Control.FOCUS_NONE
	s.tooltip_text = tip
	r.add_child(s)
	var val := _label("%d%%" % s.value, FS, PLAQUE)
	val.custom_minimum_size = Vector2(64, 0)
	val.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	r.add_child(val)
	s.value_changed.connect(_on_slider.bind(vkey, val, "%d%%", 100.0))
	var sw := CheckButton.new()
	sw.name = "Opt_" + mkey
	sw.focus_mode = Control.FOCUS_NONE
	sw.button_pressed = not settings.on(mkey)
	sw.tooltip_text = tr("Sound on / off")
	sw.toggled.connect(func(v: bool): settings.set_value(mkey, not v))
	r.add_child(sw)


func _chip_row(parent: Control, label: String, tip: String) -> HBoxContainer:
	var r := HBoxContainer.new()
	r.add_theme_constant_override("separation", 6)
	parent.add_child(r)
	var l := _label(label, FS, KEY)
	l.tooltip_text = tip
	l.mouse_filter = Control.MOUSE_FILTER_PASS
	l.custom_minimum_size = Vector2(120, 0)
	r.add_child(l)
	return r


func _mark_chip(b: Button, on: bool) -> void:
	if not on:
		return
	var st: StyleBoxFlat = _flat(BTN_GREEN, BTN_GREEN.darkened(0.35), 2, 16)
	st.content_margin_left = 14
	st.content_margin_right = 14
	st.content_margin_top = 4
	st.content_margin_bottom = 4
	for s in ["normal", "hover", "pressed"]:
		b.add_theme_stylebox_override(s, st)
	for c in ["font_color", "font_hover_color", "font_pressed_color"]:
		b.add_theme_color_override(c, Color(1, 1, 1))
	b.add_theme_color_override("font_outline_color", Color(0.12, 0.25, 0.08))
	b.add_theme_constant_override("outline_size", 3)


# ---------------------------------------------------------------- tabs

func show_tab(id: String) -> void:
	tab = id
	_end_capture()
	for k in _tab_btns:
		var b: Button = _tab_btns[k]
		var on: bool = k == id
		# wood tabs: warm brown, the open one green (style guide: left tab column)
		var st: Array = _btn_styles(BTN_GREEN if on else Color("#9a6a3a"))
		for s in st:
			(s as StyleBoxFlat).content_margin_left = 16
		b.add_theme_stylebox_override("normal", st[0])
		b.add_theme_stylebox_override("hover", st[1])
		b.add_theme_stylebox_override("pressed", st[2])
		b.add_theme_color_override("font_color", Color(1, 1, 1) if on else Color("#fff1d0"))
		b.add_theme_color_override("font_hover_color", Color(1, 1, 1))
		# UI v3: dark leather side tabs, the open one gold (ui/ui_skin.gd)
		UiSkin.style_side_tab(b, on)
	_clear(page)
	preset_lbl = null
	tone_lbl = null
	conflict_lbl = null
	match id:
		"graphics":
			_page_graphics()
		"lighting":
			_page_lighting()
		"audio":
			_page_audio()
		"interface":
			_page_interface()
		"controls":
			_page_controls()
		"language":
			_page_language()
	_update_labels()
	scroll.scroll_vertical = 0


func _page_graphics() -> void:
	var a: VBoxContainer = _au.section("Quality preset", "One click sets every graphics option below. You can fine-tune them afterwards.")
	var r := _chip_row(a, "Preset", "Low: fastest, Ultra: best looking.")
	for p in [["low", "Low"], ["medium", "Medium"], ["high", "High"], ["ultra", "Ultra"]]:
		var c: Button = _au.chip(str(p[1]), settings.apply_gfx_preset.bind(str(p[0])))
		c.name = "Preset_" + str(p[0])
		_mark_chip(c, str(settings.val("gfx_preset")) == str(p[0]))
		r.add_child(c)
	preset_lbl = _label("", FS_SMALL, INK_SOFT)
	r.add_child(preset_lbl)
	var d: VBoxContainer = _au.section("Display")
	option_row(d, "window_mode", "Window mode", "Windowed, borderless full screen (fast alt-tab) or exclusive full screen.",
		[["windowed", "Windowed"], ["borderless", "Borderless fullscreen"], ["fullscreen", "Fullscreen"]])
	var res: Array = [["", "Keep current size"]]
	var scr := DisplayServer.screen_get_size() if DisplayServer.get_name() != "headless" else Vector2i(3840, 2160)
	for s in settings.RESOLUTIONS:
		var wh: PackedStringArray = str(s).split("x")
		if int(wh[0]) <= scr.x and int(wh[1]) <= scr.y:
			res.append([s, str(s).replace("x", " x ")])
	var rr := option_row(d, "resolution", "Resolution (windowed)", "Window size in windowed mode.", res)
	rr.disabled = str(settings.val("window_mode")) != "windowed"
	var fps: Array = []
	for f in settings.FPS_CAPS:
		fps.append([int(f), tr("Unlimited") if int(f) == 0 else "%d FPS" % int(f)])
	option_row(d, "fps_cap", "FPS limit", "Caps the frame rate (saves power / heat on laptops).", fps)
	switch_row(d, "vsync", "V-Sync", "Syncs frames to the monitor: no tearing, the frame rate follows the screen.")
	var b: VBoxContainer = _au.section("Rendering")
	slider_row(b, "render_scale", "Render scale", "3D resolution. Lower = faster, a little softer. The HUD stays sharp.", 50, 100, 5, "%d%%")
	option_row(b, "upscaler", "Upscaler", "How a render scale under 100% is scaled up.", [["fsr", "AMD FSR 1.0 (sharper)"], ["linear", "Linear (softer)"]])
	option_row(b, "aa", "Anti-aliasing", "Smooths jagged edges. MSAA looks best, FXAA is cheapest.",
		[["off", "Off"], ["fxaa", "FXAA"], ["msaa2", "MSAA 2x"], ["msaa4", "MSAA 4x"]])
	var c2: VBoxContainer = _au.section("World")
	option_row(c2, "shadows", "Shadow quality", "Sharpness and reach of the sun's shadows.",
		[["off", "Off"], ["low", "Low"], ["medium", "Medium"], ["high", "High"]])
	option_row(c2, "view_distance", "View distance", "How far the camera draws, and where grass and flowers fade out.",
		[["near", "Near"], ["medium", "Medium"], ["far", "Far"], ["ultra", "Ultra"]])
	slider_row(c2, "foliage", "Grass & flower density", "Share of the grass tufts and flowers that are drawn.", 0, 100, 5, "%d%%")
	note(c2, "Grass and flowers change at once. Trees and rocks always follow the map (they block movement, so everyone must see the same ones).")
	option_row(c2, "effects", "Effects quality", "Particle count of hits, spells and sparkles.", [["low", "Low"], ["medium", "Medium"], ["high", "High"]])
	switch_row(c2, "bloom", "Bloom (glow)", "Soft glow around bright spells, lamps and sparks.")


func _page_lighting() -> void:
	var a: VBoxContainer = _au.section("Tone presets", "Ready-made looks. Every slider below can be tuned afterwards.")
	var r := _chip_row(a, "Tone", "Vivid is the default bright anime look.")
	for p in [["vivid", "Vivid"], ["soft", "Soft pastel"], ["cinematic", "Cinematic"], ["evening", "Evening"]]:
		var c: Button = _au.chip(str(p[1]), settings.apply_tone.bind(str(p[0])))
		c.name = "Tone_" + str(p[0])
		_mark_chip(c, str(settings.val("tone")) == str(p[0]))
		r.add_child(c)
	tone_lbl = _label("", FS_SMALL, INK_SOFT)
	a.add_child(tone_lbl)
	option_row(a, "color_mode", "Color mode", "Vivid: saturated Draconia-like colours. Soft: gentler, pastel colours.",
		[["vivid", "Vivid"], ["soft", "Soft"]])
	note(a, "These work together with day and night: they scale the light of the current hour, in dungeons too. Hold Peek (top) to see the scene.")
	var b: VBoxContainer = _au.section("Picture")
	slider_row(b, "brightness", "Brightness", "Overall brightness.", 50, 150, 1, "%d%%", 100.0)
	slider_row(b, "contrast", "Contrast", "Difference between light and dark.", 50, 150, 1, "%d%%", 100.0)
	slider_row(b, "saturation", "Saturation", "Colour strength (0% = black and white).", 0, 200, 1, "%d%%", 100.0)
	slider_row(b, "gamma", "Gamma", "Brightens (higher) or deepens (lower) the mid tones.", 60, 160, 1, "%d%%", 100.0)
	slider_row(b, "temperature", "Color temperature", "Cool (blue) to warm (golden) white balance.", -100, 100, 1, "temp", 100.0)
	var c3: VBoxContainer = _au.section("Light")
	slider_row(c3, "sun", "Sun intensity", "Strength of the sun (the moon at night).", 0, 200, 1, "%d%%", 100.0)
	slider_row(c3, "ambient", "Ambient light", "Light in the shade and indoors.", 0, 200, 1, "%d%%", 100.0)
	slider_row(c3, "shadow_dark", "Shadow darkness", "How dark the sun's shadows are.", 0, 150, 1, "%d%%", 100.0)
	slider_row(c3, "shadow_tint", "Shadow tint", "How much of the blue-violet sky colour the shade gets (0% = grey shade).", 0, 150, 1, "%d%%", 100.0)
	slider_row(c3, "bloom_strength", "Bloom strength", "Glow of bright spells, lamps and sparks.", 0, 250, 1, "%d%%", 100.0)
	slider_row(c3, "fog", "Fog density", "Distance haze.", 0, 300, 1, "%d%%", 100.0)


func _page_audio() -> void:
	var a: VBoxContainer = _au.section("Volume", "The switch on the right turns that sound on or off.")
	volume_row(a, "master_volume", "master_muted", "Master", "Everything the game plays.")
	volume_row(a, "music_volume", "music_muted", "Music", "Your music files (music folder). Key M = on / off, [ and ] = volume.")
	volume_row(a, "sfx_volume", "sfx_muted", "Sound effects", "Hits, spells, items, footsteps. Key N = on / off.")
	volume_row(a, "ui_volume", "ui_muted", "Interface sounds", "Button clicks, windows opening / closing.")
	note(a, "Music plays from the music folder next to the game (see README.txt there).")


func _page_interface() -> void:
	var a: VBoxContainer = _au.section("Size")
	slider_row(a, "ui_scale", "UI scale", "Size of every window, button and text.", 75, 150, 5, "%d%%", 100.0)
	option_row(a, "ui_style", "Window style", "Storybook: painted cream panels with flowers and green tabs. Classic: wood frame and parchment. Takes effect the next time the game starts.",
		[["v4", "Storybook"], ["v3", "Classic"]])
	var b: VBoxContainer = _au.section("Show on screen")
	switch_row(b, "show_damage", "Damage numbers", "Numbers over monsters and heroes when they are hit or healed.")
	switch_row(b, "show_player_names", "Player names", "Name tags over heroes (yours and other players').")
	switch_row(b, "show_monster_names", "Monster names", "Name and level over monsters.")
	switch_row(b, "show_hp_bars", "HP bars", "Health bars over heroes and monsters.")
	switch_row(b, "show_others", "Other players", "Heroes of other players (online / co-op).")
	switch_row(b, "show_others_fx", "Other players' skill effects", "Spell and skill effects cast by other players.")
	switch_row(b, "show_minimap", "Minimap", "The map at the top right (Tab still opens the world map).")
	switch_row(b, "show_chat", "Chat and log", "The message log at the bottom left (Enter still opens chat).")
	var c: VBoxContainer = _au.section("Camera shake")
	switch_row(c, "screen_shake", "Screen shake", "Small camera shakes from big hits and skills (key H).")
	switch_row(c, "crit_shake", "Shake on critical hits", "Shake the camera on your own critical hits.")


func _page_controls() -> void:
	var a: VBoxContainer = _au.section("Camera", "Hold the right mouse button to turn the camera, mouse wheel to zoom, Home to reset.")
	var modes: Array = []
	for m in settings.CAM_MODES:
		modes.append([m[0], m[1]])
	option_row(a, "cam_mode", "Camera mode", "Auto: swings behind your hero as you walk and keeps your target in view. Classic: fixed isometric angle. Free: only you move it. Key F11 cycles.", modes)
	slider_row(a, "follow_speed", "Follow speed", "How fast the Auto camera swings behind your hero.", 0.2, 3.0, 0.1, "x")
	slider_row(a, "cam_zoom", "Zoom", "Camera distance (the mouse wheel changes it too).", 5.0, 30.0, 0.5, "%.1f m")
	slider_row(a, "cam_sens", "Rotate sensitivity", "How fast the camera turns while you hold the right mouse button.", 0.25, 3.0, 0.05, "x")
	slider_row(a, "zoom_speed", "Zoom speed", "How far one mouse wheel step zooms.", 0.25, 3.0, 0.05, "x")
	switch_row(a, "invert_y", "Invert vertical", "Moving the mouse up tilts the camera down.")
	option_row(a, "touch_controls", "Touch controls", "Joystick bottom-left + action buttons bottom-right (phones / tablets). Auto: only on a touch screen.",
		[["auto", "Auto"], ["on", "On"], ["off", "Off"]])
	var b: VBoxContainer = _au.section("Keys", "Click a key, then press the new key (Alt / Ctrl / Shift combos work). Esc cancels, Backspace clears.")
	var r := HBoxContainer.new()
	r.add_theme_constant_override("separation", 6)
	b.add_child(r)
	var cl: Button = _au.chip("Classic layout", _classic_layout, "Classic keys for windows, hotbar, map and sit (Alt+E items, Alt+Q equipment, Alt+A status, Alt+S skills...). Movement and the extra keys stay.")
	cl.name = "ClassicLayout"
	r.add_child(cl)
	var rk: Button = _au.chip("Reset keys", _reset_keys, "Every key back to its default.")
	rk.name = "ResetKeys"
	r.add_child(rk)
	conflict_lbl = _label("", FS_SMALL, GOOD)
	conflict_lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	conflict_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
	r.add_child(conflict_lbl)
	var cf: Array = settings.conflicts()
	if cf.is_empty():
		conflict_lbl.text = tr("No key conflicts")
	else:
		var names: Array = []
		for k in cf:
			names.append(settings.key_name(int(k)))
		conflict_lbl.text = tr("Used twice") + ": " + ", ".join(names)
		conflict_lbl.add_theme_color_override("font_color", BAD)
	var head := HBoxContainer.new()
	b.add_child(head)
	head.add_child(_name_label("Action", "", 300.0))
	head.add_child(_grow(_label("Key", FS_SMALL, KEY)))
	head.add_child(_grow(_label("Second key", FS_SMALL, KEY)))
	var group := ""
	for act in settings.ACTIONS:
		if str(act[5]) != group:
			group = str(act[5])
			var gl := _label({"move": "Movement", "classic": "Windows and hotbar", "extra": "Extras"}.get(group, group), FS_SMALL + 1, KEY)
			b.add_child(gl)
		var row := HBoxContainer.new()
		row.add_theme_constant_override("separation", 8)
		b.add_child(row)
		row.add_child(_name_label(str(act[1]), "", 300.0))
		var cur: Array = settings.binding(str(act[0]))
		for slot in 2:
			row.add_child(_key_button(str(act[0]), slot, int(cur[slot])))
	note(b, "Fixed keys: Esc = close windows / this menu, Enter = chat, Home = reset camera, [ ] = music volume. W A S D and the arrow keys move your hero (camera-relative).")


func _key_button(action: String, slot: int, key: int) -> Button:
	var b := Button.new()
	b.name = "Key_%s_%d" % [action, slot]
	b.text = settings.key_name(key)
	b.focus_mode = Control.FOCUS_NONE
	b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	b.custom_minimum_size = Vector2(0, 34)
	b.tooltip_text = tr("Click, then press a key")
	var n := _flat(Color("#fffaf0"), Color(FRAME, 0.9), 2, 7)
	n.content_margin_top = 3
	n.content_margin_bottom = 3
	var h: StyleBoxFlat = n.duplicate()
	h.bg_color = Color("#fff1cf")
	h.border_color = Color("#c98a1c")
	b.add_theme_stylebox_override("normal", n)
	b.add_theme_stylebox_override("hover", h)
	b.add_theme_stylebox_override("pressed", h)
	for c in ["font_color", "font_hover_color", "font_pressed_color"]:
		b.add_theme_color_override(c, INK if key != KEY_NONE else INK_SOFT)
	b.add_theme_constant_override("outline_size", 0)
	b.add_theme_font_size_override("font_size", FS_SMALL + 1)
	b.auto_translate_mode = Node.AUTO_TRANSLATE_MODE_DISABLED
	b.pressed.connect(_start_capture.bind(action, slot, b))
	return b


func _start_capture(action: String, slot: int, b: Button) -> void:
	_end_capture()
	settings.capture = action
	settings.capture_slot = slot
	_capture_btn = b
	b.text = tr("Press a key...")
	var st: StyleBoxFlat = _flat(Color("#f6d68a"), Color("#b8781a"), 2, 7)
	b.add_theme_stylebox_override("normal", st)
	b.add_theme_stylebox_override("hover", st)
	_note(tr("Press the new key for") + " \"" + tr(settings.action_label(action)) + "\"  (" + tr("Esc cancels, Backspace clears") + ")")


func _end_capture() -> void:
	if settings:
		settings.capture = ""
	_capture_btn = null


## A key was pressed while waiting (Settings._input).
func captured(combo: int) -> void:
	var action: String = settings.capture
	var slot: int = settings.capture_slot
	_end_capture()
	var kc := combo & KEY_CODE_MASK
	if kc == KEY_ESCAPE:
		_note(tr("Cancelled."))
		show_tab("controls")
		return
	if kc in [KEY_BACKSPACE, KEY_DELETE]:
		settings.unbind(action, slot)
		_note(tr(settings.action_label(action)) + ": " + tr("no key"))
		return
	var other: String = settings.bind(action, slot, combo)
	if other == "!":
		_note(settings.key_name(combo) + ": " + tr("this key is reserved (Esc, Enter, Home, [ ])."), BAD)
		show_tab("controls")
	elif other != "":
		_note(settings.key_name(combo) + " " + tr("was used by") + " \"" + tr(settings.action_label(other)) + "\" - " + tr("the two keys were swapped."), Color("#9a6200"))
	else:
		_note(tr(settings.action_label(action)) + ": " + settings.key_name(combo), GOOD)


func _classic_layout() -> void:
	settings.classic_layout()
	_note(tr("Classic layout set."))


func _reset_keys() -> void:
	settings.reset_keys()
	_note(tr("Every key is back to its default."))


func _page_language() -> void:
	var a: VBoxContainer = _au.section("Language", "Language of the menus.")
	var r := _chip_row(a, "Language", "")
	for l in settings.LANGS:
		var c: Button = _au.chip(str(l[1]), settings.set_value.bind("language", str(l[0])))
		c.name = "Lang_" + str(l[0])
		c.auto_translate_mode = Node.AUTO_TRANSLATE_MODE_DISABLED
		_mark_chip(c, str(settings.val("language")) == str(l[0]))
		r.add_child(c)
	note(a, "Thai covers this window and the main HUD buttons and windows. Names of items, monsters and skills, quest text and chat stay in English for now.")
	var th := note(a, "ภาษาไทย: หน้าตั้งค่านี้ + ปุ่ม/หน้าต่างหลักของ HUD · ชื่อไอเทม/มอน/สกิล เควส และแชท ยังเป็นภาษาอังกฤษ")
	th.auto_translate_mode = Node.AUTO_TRANSLATE_MODE_DISABLED
