extends Node
## Phase 19 — Onboarding hint tour orchestrator.
##
## Reads GameState.settings["show_hints"] and ["dismissed_hints"] at _ready.
## Connects signal-based triggers for the 7-step tour; queues hints when the
## overlay is busy (max depth 3, oldest dropped first on overflow).
##
## The autoload name is HintSystem.  HintOverlay registers itself here in its
## own _ready so this singleton never calls find_child() on the tree.
##
## Tour steps in order:
##   welcome → pawn_select → build_drawer → stockpile_painted →
##   work_matrix → day_ended → tour_complete (auto-fired after day_ended)

# ── tour definition ──────────────────────────────────────────────────────────

## Each entry: { id, message_key }  — trigger wiring is in _connect_triggers().
const TOUR: Array = [
	{ "id": &"welcome",           "msg": &"hint.welcome"           },
	{ "id": &"pawn_select",       "msg": &"hint.pawn_select"       },
	{ "id": &"build_drawer",      "msg": &"hint.build_drawer"      },
	{ "id": &"stockpile_painted", "msg": &"hint.stockpile_painted" },
	{ "id": &"work_matrix",       "msg": &"hint.work_matrix"       },
	{ "id": &"day_ended",         "msg": &"hint.day_ended"         },
	{ "id": &"tour_complete",     "msg": &"hint.tour_complete"     },
]

const MAX_QUEUE: int = 3

# ── state ─────────────────────────────────────────────────────────────────────

var _overlay: Node = null          # HintOverlay — registered via register_overlay()
var _queue: Array  = []            # Array[StringName] pending hint ids
var _overlay_busy: bool = false    # true while overlay is sliding in or showing
var _enabled: bool = false         # false if show_hints == false or all dismissed


func _ready() -> void:
	process_mode = Node.PROCESS_MODE_ALWAYS
	var show: bool = bool(GameState.settings.get("show_hints", true))
	if not show:
		Audit.log("hint_system", "HintSystem disabled (show_hints=false)")
		return
	_enabled = true
	_connect_triggers()
	Audit.log("hint_system", "HintSystem ready")


# ── public API ───────────────────────────────────────────────────────────────

## Called by HintOverlay._ready() to register itself with this singleton.
func register_overlay(overlay: Node) -> void:
	_overlay = overlay
	Audit.log("hint_system", "HintOverlay registered")


## Reset the full tour: clear dismissed list, re-enable, refire welcome.
## Called by HelpModal's "Reset hints" button.
func reset_tour() -> void:
	GameState.settings["dismissed_hints"] = [] as Array
	_queue.clear()
	_overlay_busy = false
	_enabled = true
	if not _are_triggers_connected():
		_connect_triggers()
	_try_show(&"welcome")
	Audit.log("hint_system", "tour reset")


# ── trigger wiring ───────────────────────────────────────────────────────────

func _are_triggers_connected() -> bool:
	return EventBus.pawn_selected.is_connected(_on_pawn_selected)


func _connect_triggers() -> void:
	# welcome — deferred 2s timer, only if not already dismissed.
	if not _is_dismissed(&"welcome"):
		get_tree().create_timer(2.0).timeout.connect(_on_welcome_timer)

	# pawn_select
	if not EventBus.pawn_selected.is_connected(_on_pawn_selected):
		EventBus.pawn_selected.connect(_on_pawn_selected)

	# build_drawer + work_matrix share ui_panel_opened
	if not EventBus.ui_panel_opened.is_connected(_on_ui_panel_opened):
		EventBus.ui_panel_opened.connect(_on_ui_panel_opened)

	# stockpile_painted
	if not EventBus.designation_added.is_connected(_on_designation_added):
		EventBus.designation_added.connect(_on_designation_added)

	# day_ended
	if not EventBus.day_ended.is_connected(_on_day_ended):
		EventBus.day_ended.connect(_on_day_ended)

	# hint_dismissed — HintOverlay emits this; we persist and dequeue.
	if not EventBus.hint_dismissed.is_connected(_on_hint_dismissed):
		EventBus.hint_dismissed.connect(_on_hint_dismissed)


# ── trigger handlers ─────────────────────────────────────────────────────────

func _on_welcome_timer() -> void:
	_try_show(&"welcome")


func _on_pawn_selected(_pawn) -> void:
	_try_show(&"pawn_select")


func _on_ui_panel_opened(panel_id: StringName) -> void:
	match panel_id:
		&"build_drawer":
			_try_show(&"build_drawer")
		&"work_matrix":
			_try_show(&"work_matrix")


func _on_designation_added(_cell: Vector2i, tool: StringName) -> void:
	if tool == &"paint_stockpile":
		_try_show(&"stockpile_painted")


func _on_day_ended(_summary: Dictionary) -> void:
	_try_show(&"day_ended")


func _on_hint_dismissed(hint_id: StringName) -> void:
	# Persist dismissal (String, not StringName, for JSON-safe save).
	var dismissed: Array = GameState.settings["dismissed_hints"]
	var id_str: String = String(hint_id)
	if not dismissed.has(id_str):
		dismissed.append(id_str)
		GameState.settings["dismissed_hints"] = dismissed

	_overlay_busy = false
	Audit.log("hint_system", "dismissed: %s  total=%d" % [id_str, dismissed.size()])

	# Auto-fire tour_complete after day_ended is dismissed.
	if hint_id == &"day_ended":
		_try_show(&"tour_complete")

	# Drain the queue.
	_drain_queue()


# ── show / queue logic ───────────────────────────────────────────────────────

func _try_show(hint_id: StringName) -> void:
	if not _enabled:
		return
	if _is_dismissed(hint_id):
		return
	if _overlay_busy:
		_enqueue(hint_id)
		return
	_show_now(hint_id)


func _show_now(hint_id: StringName) -> void:
	if _overlay == null:
		# Overlay not yet mounted — queue for later.
		_enqueue(hint_id)
		return
	var msg_key: StringName = _msg_key_for(hint_id)
	_overlay_busy = true
	_overlay.show_hint(hint_id, Strings.t(msg_key))


func _enqueue(hint_id: StringName) -> void:
	if _queue.has(hint_id):
		return  # already pending
	if _queue.size() >= MAX_QUEUE:
		_queue.pop_front()  # drop oldest to cap backlog
	_queue.append(hint_id)


func _drain_queue() -> void:
	while _queue.size() > 0:
		var next: StringName = _queue[0]
		_queue.pop_front()
		if _is_dismissed(next):
			continue  # skip already-dismissed entries
		_show_now(next)
		return   # show one at a time; wait for next hint_dismissed


# ── helpers ──────────────────────────────────────────────────────────────────

func _is_dismissed(hint_id: StringName) -> bool:
	var dismissed: Array = GameState.settings.get("dismissed_hints", [])
	return dismissed.has(String(hint_id))


func _msg_key_for(hint_id: StringName) -> StringName:
	for entry in TOUR:
		if entry["id"] == hint_id:
			return entry["msg"]
	return &"hint.welcome"  # fallback
