class_name WorkbenchPanel extends CanvasLayer
## Phase 17 — Right-side bottom-sheet workbench bill editor.
##
## Layer 18: same level as PawnDetailPanel (only one is visible at a time).
## Opens when EventBus.workbench_selected fires; closes on workbench_deselected
## or when a pawn is selected (mutual-exclusion with PawnDetailPanel).
##
## Refresh model (matches PawnDetailPanel):
##   - Full UI rebuild: only on workbench_selected, add_bill, remove_bill.
##   - Status-line refresh: every 5 sim ticks while open (_on_sim_tick).
##   - Bill rows are NOT touched during refresh to preserve scroll + dropdown state.
##
## Touch targets: all interactive controls are at least 48×48 px.
## Background elements use MOUSE_FILTER_IGNORE so world taps pass through.

const PANEL_WIDTH: int = 360
const REFRESH_TICKS: int = 5   # update status line every N sim ticks
const LAYER: int = 18

# ── internal state ────────────────────────────────────────────────────────────
var current_workbench: Workbench = null
var _tick_counter: int = 0

# ── node refs (built in _build_ui) ───────────────────────────────────────────
var _panel: PanelContainer = null
var _wb_name_label: Label = null
var _close_btn: Button = null
var _status_label: Label = null
var _bills_vbox: VBoxContainer = null
var _add_btn: Button = null
var _no_bills_label: Label = null
var _recipe_popup: PopupMenu = null


func _ready() -> void:
	layer = LAYER

	_build_ui()
	_set_visible(false)

	EventBus.workbench_selected.connect(_on_workbench_selected)
	EventBus.workbench_deselected.connect(_on_workbench_deselected)
	EventBus.pawn_selected.connect(_on_pawn_selected)
	EventBus.sim_tick.connect(_on_sim_tick)

	Audit.log("workbench_panel", "WorkbenchPanel ready (layer %d)" % layer)


func _exit_tree() -> void:
	if EventBus.workbench_selected.is_connected(_on_workbench_selected):
		EventBus.workbench_selected.disconnect(_on_workbench_selected)
	if EventBus.workbench_deselected.is_connected(_on_workbench_deselected):
		EventBus.workbench_deselected.disconnect(_on_workbench_deselected)
	if EventBus.pawn_selected.is_connected(_on_pawn_selected):
		EventBus.pawn_selected.disconnect(_on_pawn_selected)
	if EventBus.sim_tick.is_connected(_on_sim_tick):
		EventBus.sim_tick.disconnect(_on_sim_tick)


# ── UI construction ───────────────────────────────────────────────────────────

func _build_ui() -> void:
	# Right-side sheet anchored to the right edge, full height.
	_panel = PanelContainer.new()
	_panel.name = "WorkbenchSheet"
	_panel.anchor_left   = 1.0
	_panel.anchor_right  = 1.0
	_panel.anchor_top    = 0.0
	_panel.anchor_bottom = 1.0
	_panel.offset_left   = -PANEL_WIDTH
	_panel.offset_right  = 0.0
	_panel.offset_top    = 0.0
	_panel.offset_bottom = 0.0
	_panel.mouse_filter = Control.MOUSE_FILTER_PASS
	add_child(_panel)

	# Scrollable inner container so content survives small screens.
	var scroll := ScrollContainer.new()
	scroll.name = "Scroll"
	scroll.set_anchors_preset(Control.PRESET_FULL_RECT)
	scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
	_panel.add_child(scroll)

	var vbox := VBoxContainer.new()
	vbox.name = "Content"
	vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	vbox.add_theme_constant_override("separation", 6)
	scroll.add_child(vbox)

	# ── Header ────────────────────────────────────────────────────────────────
	var header := HBoxContainer.new()
	header.name = "Header"
	header.add_theme_constant_override("separation", 8)
	vbox.add_child(header)

	_wb_name_label = Label.new()
	_wb_name_label.name = "WorkbenchName"
	_wb_name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	_wb_name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	_wb_name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
	header.add_child(_wb_name_label)

	_close_btn = Button.new()
	_close_btn.name = "CloseBtn"
	_close_btn.text = Strings.t(&"ui.detail.close")
	_close_btn.custom_minimum_size = Vector2(48, 48)
	_close_btn.focus_mode = Control.FOCUS_NONE
	_close_btn.pressed.connect(_on_close_pressed)
	header.add_child(_close_btn)

	_add_separator(vbox)

	# ── Status line (live-refreshed) ──────────────────────────────────────────
	_status_label = Label.new()
	_status_label.name = "StatusLabel"
	_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
	_status_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
	vbox.add_child(_status_label)

	_add_separator(vbox)

	# ── Bill list ─────────────────────────────────────────────────────────────
	var bills_header := Label.new()
	bills_header.text = Strings.t(&"ui.bill.no_bills_hint")
	bills_header.name = "BillsHeader"
	bills_header.mouse_filter = Control.MOUSE_FILTER_IGNORE
	# Header is just section spacing; actual content is in _bills_vbox below.
	# Repurpose this as the "no bills" hint — hidden when bills exist.
	_no_bills_label = bills_header
	vbox.add_child(_no_bills_label)

	_bills_vbox = VBoxContainer.new()
	_bills_vbox.name = "BillList"
	_bills_vbox.add_theme_constant_override("separation", 8)
	_bills_vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
	vbox.add_child(_bills_vbox)

	_add_separator(vbox)

	# ── Add-bill footer ───────────────────────────────────────────────────────
	var footer := HBoxContainer.new()
	footer.name = "Footer"
	footer.add_theme_constant_override("separation", 8)
	vbox.add_child(footer)

	_add_btn = Button.new()
	_add_btn.name = "AddBillBtn"
	_add_btn.text = Strings.t(&"ui.bill.add_button")
	_add_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	_add_btn.custom_minimum_size = Vector2(0, 48)
	_add_btn.focus_mode = Control.FOCUS_NONE
	_add_btn.pressed.connect(_on_add_bill_pressed)
	footer.add_child(_add_btn)

	# PopupMenu for recipe selection — populated lazily in _on_add_bill_pressed.
	_recipe_popup = PopupMenu.new()
	_recipe_popup.name = "RecipePopup"
	_recipe_popup.id_pressed.connect(_on_recipe_chosen)
	_panel.add_child(_recipe_popup)


func _add_separator(parent: VBoxContainer) -> void:
	var sep := HSeparator.new()
	sep.mouse_filter = Control.MOUSE_FILTER_IGNORE
	parent.add_child(sep)


func _clear_children(node: Node) -> void:
	for child in node.get_children():
		child.queue_free()


# ── event handlers ────────────────────────────────────────────────────────────

func _on_workbench_selected(wb: Workbench) -> void:
	current_workbench = wb
	_tick_counter = 0
	_wb_name_label.text = wb.label_text
	_refresh_status()
	_populate_bills()
	_set_visible(true)
	Audit.log("workbench_panel", "opened for %s" % wb.label_text)


func _on_workbench_deselected() -> void:
	current_workbench = null
	_set_visible(false)
	Audit.log("workbench_panel", "closed (deselected)")


func _on_pawn_selected(_pawn) -> void:
	# Mutual exclusion: pawn panel and workbench panel are never both open.
	if current_workbench == null:
		return
	current_workbench = null
	_set_visible(false)
	EventBus.workbench_deselected.emit()
	Audit.log("workbench_panel", "closed (pawn selected)")


func _on_close_pressed() -> void:
	current_workbench = null
	_set_visible(false)
	EventBus.workbench_deselected.emit()
	Audit.log("workbench_panel", "closed (X button)")


func _on_sim_tick(_tick_number: int) -> void:
	if current_workbench == null or not _panel.visible:
		return
	if not is_instance_valid(current_workbench):
		current_workbench = null
		_set_visible(false)
		return
	_tick_counter += 1
	if _tick_counter >= REFRESH_TICKS:
		_tick_counter = 0
		_refresh_status()


# ── status-line refresh (called every REFRESH_TICKS, NOT rebuild) ─────────────

func _refresh_status() -> void:
	if current_workbench == null:
		return
	var cb = current_workbench.current_bill
	if cb != null and cb.recipe != null:
		var work_ticks: int = cb.recipe.work_ticks
		_status_label.text = "%s: %s  %d/%d" % [
			Strings.t(&"ui.workbench.current_bill"),
			cb.recipe.display_name(),
			current_workbench.current_work_progress,
			work_ticks
		]
	else:
		_status_label.text = Strings.t(&"ui.workbench.idle")


# ── bill list population (called on open / add / remove only) ─────────────────

func _populate_bills() -> void:
	if current_workbench == null:
		return

	_clear_children(_bills_vbox)

	var has_bills: bool = current_workbench.bills.size() > 0
	_no_bills_label.visible = not has_bills

	for bill in current_workbench.bills:
		_bills_vbox.add_child(_make_bill_row(bill))

	# Enable/disable the add button based on whether any filtered recipes exist.
	var filtered: Array[Recipe] = _filtered_recipes()
	_add_btn.disabled = filtered.is_empty()


## Build and return a VBoxContainer widget for a single bill.
## All controls mutate bill fields directly; remove triggers _populate_bills().
func _make_bill_row(bill: Bill) -> VBoxContainer:
	var row_vbox := VBoxContainer.new()
	row_vbox.add_theme_constant_override("separation", 4)
	row_vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE

	# Row 1: recipe name (bold via theme; we use a plain Label — theme handles weight).
	var name_lbl := Label.new()
	name_lbl.text = bill.recipe.display_name() if bill.recipe != null else "???"
	name_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
	row_vbox.add_child(name_lbl)

	# Row 2: mode OptionButton (Forever / Do X times / Do until X).
	var mode_row := HBoxContainer.new()
	mode_row.add_theme_constant_override("separation", 6)
	row_vbox.add_child(mode_row)

	var mode_lbl := Label.new()
	mode_lbl.text = "Mode:"
	mode_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
	mode_lbl.custom_minimum_size = Vector2(40, 0)
	mode_row.add_child(mode_lbl)

	var mode_btn := OptionButton.new()
	mode_btn.add_item(Strings.t(&"ui.bill.mode_forever"), Bill.Mode.FOREVER)
	mode_btn.add_item(Strings.t(&"ui.bill.mode_count"),   Bill.Mode.COUNT)
	mode_btn.add_item(Strings.t(&"ui.bill.mode_until_n"), Bill.Mode.UNTIL_N)
	mode_btn.selected = bill.mode as int
	mode_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	mode_btn.focus_mode = Control.FOCUS_NONE
	mode_btn.custom_minimum_size = Vector2(0, 48)
	# Capture bill reference in closure; repopulate on mode change so conditional
	# rows (count spinner, done label) appear/disappear correctly.
	mode_btn.item_selected.connect(func(idx: int) -> void:
		bill.mode = idx as Bill.Mode
		Audit.log("workbench_ui", "%s: bill mode → %d" % [current_workbench.label_text, idx])
		# Defer the rebuild — we must NOT free mode_btn while its item_selected
		# signal is still emitting (instant crash).  call_deferred runs the
		# repopulate after the signal frame completes.
		call_deferred("_populate_bills")
	)
	mode_row.add_child(mode_btn)

	# Row 3 (conditional): SpinBox for target_count. Shown when mode != FOREVER.
	if bill.mode != Bill.Mode.FOREVER:
		var count_row := HBoxContainer.new()
		count_row.add_theme_constant_override("separation", 6)
		row_vbox.add_child(count_row)

		var count_lbl := Label.new()
		count_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
		count_lbl.custom_minimum_size = Vector2(80, 0)
		if bill.mode == Bill.Mode.COUNT:
			count_lbl.text = Strings.t(&"ui.bill.target")
		else:
			count_lbl.text = Strings.t(&"ui.bill.until_count")
		count_row.add_child(count_lbl)

		var spin := SpinBox.new()
		spin.min_value = 1
		spin.max_value = 999
		spin.step = 1
		spin.value = max(1, bill.target_count)
		spin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
		spin.focus_mode = Control.FOCUS_NONE
		spin.value_changed.connect(func(v: float) -> void:
			bill.target_count = int(v)
			Audit.log("workbench_ui", "%s: bill target_count → %d" % [current_workbench.label_text, bill.target_count])
		)
		count_row.add_child(spin)

	# Row 4 (COUNT only): "Done: X/Y" progress label.
	if bill.mode == Bill.Mode.COUNT:
		var done_lbl := Label.new()
		done_lbl.text = "%s: %d/%d" % [Strings.t(&"ui.bill.completed"), bill.completed_count, bill.target_count]
		done_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
		row_vbox.add_child(done_lbl)

	# Row 5: pause CheckBox.
	var pause_check := CheckBox.new()
	pause_check.text = Strings.t(&"ui.bill.pause")
	pause_check.button_pressed = bill.paused
	pause_check.focus_mode = Control.FOCUS_NONE
	pause_check.custom_minimum_size = Vector2(0, 40)
	pause_check.toggled.connect(func(on: bool) -> void:
		bill.paused = on
		Audit.log("workbench_ui", "%s: bill paused → %s" % [current_workbench.label_text, str(on)])
	)
	row_vbox.add_child(pause_check)

	# Row 6: "Remove" button, right-aligned via HSpacer + HBox.
	var remove_row := HBoxContainer.new()
	row_vbox.add_child(remove_row)

	var spacer := Control.new()
	spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	spacer.mouse_filter = Control.MOUSE_FILTER_IGNORE
	remove_row.add_child(spacer)

	var remove_btn := Button.new()
	remove_btn.text = Strings.t(&"ui.bill.remove")
	remove_btn.custom_minimum_size = Vector2(80, 40)
	remove_btn.focus_mode = Control.FOCUS_NONE
	remove_btn.pressed.connect(func() -> void:
		if current_workbench == null:
			return
		current_workbench.remove_bill(bill)
		Audit.log("workbench_ui", "%s: bill removed — recipe '%s'" % [
			current_workbench.label_text,
			bill.recipe.id if bill.recipe != null else "null"
		])
		# Defer — same reason as mode_btn: don't free this button mid-emit.
		call_deferred("_populate_bills")
	)
	remove_row.add_child(remove_btn)

	# Thin separator below each bill row for visual grouping.
	var sep := HSeparator.new()
	sep.mouse_filter = Control.MOUSE_FILTER_IGNORE
	row_vbox.add_child(sep)

	return row_vbox


# ── add-bill popup ────────────────────────────────────────────────────────────

func _on_add_bill_pressed() -> void:
	if current_workbench == null:
		return

	var recipes: Array[Recipe] = _filtered_recipes()
	if recipes.is_empty():
		return

	_recipe_popup.clear()
	for i in recipes.size():
		_recipe_popup.add_item(recipes[i].display_name(), i)

	# Position the popup just above the add button.
	var btn_rect: Rect2 = _add_btn.get_global_rect()
	_recipe_popup.position = Vector2i(int(btn_rect.position.x), int(btn_rect.position.y) - _recipe_popup.size.y - 4)
	_recipe_popup.popup()


func _on_recipe_chosen(id: int) -> void:
	if current_workbench == null:
		return
	var recipes: Array[Recipe] = _filtered_recipes()
	if id < 0 or id >= recipes.size():
		return
	var picked: Recipe = recipes[id]
	var b := Bill.new()
	b.recipe = picked
	b.mode = Bill.Mode.FOREVER
	current_workbench.add_bill(b)
	Audit.log("workbench_ui", "%s: bill added — recipe '%s'" % [current_workbench.label_text, picked.id])
	_populate_bills()


## Returns all catalog recipes visible at the current workbench.
## Two-pass filter:
##   1. required_skill must match workbench.accepted_skill.
##   2. If the recipe has a target_workbench set, it must match
##      workbench.label_text.to_lower() — e.g. "Carpenter" → "carpenter".
##      Recipes with an empty target_workbench pass to any matching-skill bench.
## Returns an empty array when no workbench is set.
func _filtered_recipes() -> Array[Recipe]:
	if current_workbench == null:
		return []
	var workbench_key: StringName = StringName(current_workbench.label_text.to_lower())
	var result: Array[Recipe] = []
	for r in RecipeCatalog.all():
		if r.required_skill != current_workbench.accepted_skill:
			continue
		if r.target_workbench != &"" and r.target_workbench != workbench_key:
			continue
		result.append(r)
	return result


# ── visibility ────────────────────────────────────────────────────────────────

func _set_visible(v: bool) -> void:
	if _panel != null:
		_panel.visible = v
