class_name PrinterManagerClass
extends Node

# Registered printers
var printers: Dictionary = {}  # printer_id -> Printer

# Signals
signal printer_registered(printer_id: String)
signal any_print_started(printer_id: String, product: ProductData)
signal any_print_completed(printer_id: String, product: ProductData)
signal any_print_failed(printer_id: String, product: ProductData, reason: String)
signal any_state_changed(printer_id: String, old_state: Printer.State, new_state: Printer.State)

func _ready() -> void:
	call_deferred("_verify")
	print("[PrinterManager] initialized")


func _verify() -> void:
	# Verify get_idle_printers() filter logic excludes new states
	assert(Printer.State.WAITING_PICKUP != Printer.State.IDLE,
		"WAITING_PICKUP must not equal IDLE for get_idle_printers filter")
	assert(Printer.State.NEEDS_CLEANING != Printer.State.IDLE,
		"NEEDS_CLEANING must not equal IDLE for get_idle_printers filter")

	# Verify _state_to_string handles all 8 states
	var all_states = [
		Printer.State.IDLE, Printer.State.PRINTING, Printer.State.DONE,
		Printer.State.FAILED, Printer.State.MAINTENANCE, Printer.State.RESERVED_RESEARCH,
		Printer.State.WAITING_PICKUP, Printer.State.NEEDS_CLEANING
	]
	for s in all_states:
		var str_val = _state_to_string(s)
		assert(str_val != "" and str_val != "idle" or s == Printer.State.IDLE,
			"_state_to_string must return unique value for state %d" % s)

	# Verify _state_to_string returns correct strings for new states
	assert(_state_to_string(Printer.State.WAITING_PICKUP) == "waiting_pickup",
		"_state_to_string(WAITING_PICKUP) should be 'waiting_pickup'")
	assert(_state_to_string(Printer.State.NEEDS_CLEANING) == "needs_cleaning",
		"_state_to_string(NEEDS_CLEANING) should be 'needs_cleaning'")

	# Verify get_total_production_stats returns a Dictionary
	assert(get_total_production_stats() is Dictionary,
		"get_total_production_stats() must return a Dictionary")

	# Verify all original state strings are correct
	assert(_state_to_string(Printer.State.IDLE) == "idle", "IDLE string mismatch")
	assert(_state_to_string(Printer.State.PRINTING) == "printing", "PRINTING string mismatch")
	assert(_state_to_string(Printer.State.DONE) == "finished", "DONE string mismatch")
	assert(_state_to_string(Printer.State.FAILED) == "failed", "FAILED string mismatch")
	assert(_state_to_string(Printer.State.MAINTENANCE) == "maintenance", "MAINTENANCE string mismatch")
	assert(_state_to_string(Printer.State.RESERVED_RESEARCH) == "research", "RESEARCH string mismatch")

	# Old save format loads without error (test id won't match any real printer)
	var old_save_data: Array = [
		{"printer_id": "_test_old_save_", "state": "IDLE", "print_progress": 0.0, "time_remaining": 0.0, "time_total": 0.0}
	]
	deserialize(old_save_data)

	# Backward compat: WAITING_PICKUP without auto_clear_timer loads without error
	var waiting_save: Array = [
		{"printer_id": "_test_waiting_", "state": "WAITING_PICKUP",
		 "print_progress": 1.0, "time_remaining": 0.0, "time_total": 0.0}
	]
	deserialize(waiting_save)

	# Backward compat: old save with auto_clear_timer field is ignored gracefully
	var old_timer_save: Array = [
		{"printer_id": "_test_timer_", "state": "WAITING_PICKUP",
		 "print_progress": 1.0, "time_remaining": 0.0, "time_total": 0.0,
		 "auto_clear_timer": 3.5}
	]
	deserialize(old_timer_save)

	print("[PrinterManager] PASS: coexistence state mapping verified")

func register_printer(printer: Printer) -> void:
	if not is_instance_valid(printer):
		return
	printers[printer.printer_id] = printer

	# Connect to printer signals to relay them globally
	printer.print_started.connect(_on_print_started)
	printer.print_completed.connect(_on_print_completed)
	printer.print_failed.connect(_on_print_failed)
	printer.state_changed.connect(_on_state_changed)

	printer_registered.emit(printer.printer_id)
	print("[PrinterManager] Registered printer: %s" % printer.printer_id)

	# Eureka hook: own a tier 5 printer
	if has_node("/root/ResearchManager") and get_max_printer_tier() >= 5:
		ResearchManager.check_eureka("own_tier5_printer")

func get_printer(id: String) -> Printer:
	return printers.get(id)

func get_idle_printers() -> Array[Printer]:
	var result: Array[Printer] = []
	for p in printers.values():
		if is_instance_valid(p) and p.state == Printer.State.IDLE:
			result.append(p)
	return result

func get_all_printers() -> Array[Printer]:
	var result: Array[Printer] = []
	for p in printers.values():
		if is_instance_valid(p):
			result.append(p)
	return result

func get_max_printer_tier() -> int:
	## Returns the highest printer_tier among all registered printers.
	var max_tier: int = 0
	for p in printers.values():
		if is_instance_valid(p) and p.printer_tier > max_tier:
			max_tier = p.printer_tier
	return max_tier

func get_printer_data(printer_id: String) -> Dictionary:
	var p = get_printer(printer_id)
	if not is_instance_valid(p):
		return {}
	return {
		"id": p.printer_id,
		"name": "FDM Printer #%d" % (printers.keys().find(printer_id) + 1),
		"status": _state_to_string(p.state),
		"level": 1,
		"speed_level": 1,
		"quality_level": 1,
		"speed_multiplier": 1.0,
		"quality_multiplier": p.quality_level,
		"current_product": p.current_product.display_name if p.current_product else "",
		"progress": p.print_progress,
		"time_remaining": p.print_time_remaining,
		"time_total": p.print_time_total,
		"total_prints": _sum_production_stats(p),
		"production_stats": p.get_production_stats(),
	}

func _state_to_string(s: Printer.State) -> String:
	match s:
		Printer.State.IDLE: return "idle"
		Printer.State.PRINTING: return "printing"
		Printer.State.DONE: return "finished"
		Printer.State.FAILED: return "failed"
		Printer.State.MAINTENANCE: return "maintenance"
		Printer.State.RESERVED_RESEARCH: return "research"
		Printer.State.WAITING_PICKUP: return "waiting_pickup"
		Printer.State.NEEDS_CLEANING: return "needs_cleaning"
	return "idle"

func reserve_printer_for_research(min_tier: int = 1) -> String:
	## Reserves an idle printer for R&D. Returns printer_id or "" if none available.
	## Prefers the lowest-tier idle printer that meets min_tier requirement.
	var best: Printer = null
	for p in printers.values():
		if not is_instance_valid(p):
			continue
		if p.state != Printer.State.IDLE:
			continue
		if p.printer_tier < min_tier:
			continue
		if best == null or p.printer_tier < best.printer_tier:
			best = p
	if best:
		best._set_state(Printer.State.RESERVED_RESEARCH)
		print("[PrinterManager] Reserved printer %s for research" % best.printer_id)
		return best.printer_id
	return ""

func release_printer(printer_id: String) -> void:
	## Releases a printer from research reservation back to IDLE.
	var p = get_printer(printer_id)
	if is_instance_valid(p) and p.state == Printer.State.RESERVED_RESEARCH:
		p._set_state(Printer.State.IDLE)
		print("[PrinterManager] Released printer %s from research" % printer_id)

func has_idle_printer(min_tier: int = 1) -> bool:
	## Check if there's at least one idle printer that meets the tier requirement.
	for p in printers.values():
		if is_instance_valid(p) and p.state == Printer.State.IDLE and p.printer_tier >= min_tier:
			return true
	return false

func get_total_production_stats() -> Dictionary:
	## Aggregate _production_stats across all registered printers.
	var totals: Dictionary = {}
	for p in printers.values():
		if not is_instance_valid(p):
			continue
		for product_id in p._production_stats:
			totals[product_id] = totals.get(product_id, 0) + p._production_stats[product_id]
	return totals


func _sum_production_stats(p: Printer) -> int:
	var total: int = 0
	for count in p._production_stats.values():
		total += count
	return total


func start_print_on(printer_id: String, product: ProductData, material: MaterialData) -> bool:
	var printer = get_printer(printer_id)
	if not is_instance_valid(printer):
		return false
	return printer.start_print(product, material)

# --- Signal relays ---

func _on_print_started(printer_id: String, product: ProductData) -> void:
	any_print_started.emit(printer_id, product)

func _on_print_completed(printer_id: String, product: ProductData) -> void:
	any_print_completed.emit(printer_id, product)
	# Track daily print types for eureka
	if product and has_node("/root/ResearchManager") and ResearchManager.has_method("record_daily_print"):
		ResearchManager.record_daily_print(product.id)

func _on_print_failed(printer_id: String, product: ProductData, reason: String) -> void:
	any_print_failed.emit(printer_id, product, reason)

func _on_state_changed(printer_id: String, old_state: Printer.State, new_state: Printer.State) -> void:
	any_state_changed.emit(printer_id, old_state, new_state)

func serialize() -> Array:
	var result: Array = []
	for p in printers.values():
		if not is_instance_valid(p):
			continue
		var state_name = "IDLE"
		match p.state:
			Printer.State.IDLE: state_name = "IDLE"
			Printer.State.PRINTING: state_name = "PRINTING"
			Printer.State.DONE: state_name = "DONE"
			Printer.State.FAILED: state_name = "FAILED"
			Printer.State.MAINTENANCE: state_name = "MAINTENANCE"
			Printer.State.RESERVED_RESEARCH: state_name = "RESERVED_RESEARCH"
			Printer.State.WAITING_PICKUP: state_name = "WAITING_PICKUP"
			Printer.State.NEEDS_CLEANING: state_name = "NEEDS_CLEANING"
		var entry: Dictionary = {
			"printer_id": p.printer_id,
			"state": state_name,
			"print_progress": p.print_progress,
			"time_remaining": p.print_time_remaining,
			"time_total": p.print_time_total,
			"is_free_printing": p.is_free_printing,
			"production_stats": p._production_stats.duplicate(),
		}
		if p.current_product:
			entry["current_product_id"] = p.current_product.id
		if p.current_material:
			entry["current_material_id"] = p.current_material.id
		# Free-print profile ID for restore
		if p.is_free_printing and p.state == Printer.State.PRINTING and not p.current_profile.is_empty():
			entry["current_profile_id"] = p.current_profile.get("profile_id", "")
		# Plate and filament state
		entry["plate_dirty"] = p.plate_dirty
		entry["loaded_material_id"] = p.loaded_material_id
		entry["loaded_material_grams"] = p.loaded_material_grams
		result.append(entry)
	return result

func deserialize(data: Array) -> void:
	var product_db = get_tree().root.get_node_or_null("Main/Systems/ProductDatabase")
	var mat_db = get_tree().root.get_node_or_null("Main/Systems/MaterialDatabase")
	for pd in data:
		var pid = pd.get("printer_id", "")
		var printer = get_printer(pid)
		if not is_instance_valid(printer):
			continue

		# Restore production stats for ALL printers (backward compat via .get())
		printer._production_stats = pd.get("production_stats", {})

		# Restore plate and filament state (backward compat: defaults for old saves)
		printer.plate_dirty = pd.get("plate_dirty", false)
		printer.loaded_material_id = pd.get("loaded_material_id", "")
		printer.loaded_material_grams = float(pd.get("loaded_material_grams", 0.0))

		var state_str = pd.get("state", "IDLE")

		if state_str == "PRINTING":
			var is_free = pd.get("is_free_printing", false)
			if is_free:
				# Free-print restore: look up profile from SlicerManager
				var profile_id: String = pd.get("current_profile_id", "")
				var profile: Dictionary = {}
				if has_node("/root/SlicerManager") and not profile_id.is_empty():
					if SlicerManager.has_method("get_profile"):
						profile = SlicerManager.get_profile(profile_id)
					else:
						profile = SlicerManager.saved_profiles.get(profile_id, {})
				if profile.is_empty():
					push_warning("[PrinterManager] Free-print profile '%s' no longer exists, resetting to IDLE" % profile_id)
					# Profile deleted between sessions — graceful fallback
				else:
					var product_data: ProductData = null
					var material_data: MaterialData = null
					if is_instance_valid(product_db):
						product_data = (product_db as ProductDatabase).get_product(pd.get("current_product_id", ""))
					if is_instance_valid(mat_db):
						material_data = (mat_db as MaterialDatabase).get_material(pd.get("current_material_id", "pla"))
					printer.is_free_printing = true
					printer.current_profile = profile
					printer.current_product = product_data
					printer.current_material = material_data
					printer.print_time_total = pd.get("time_total", 0.0)
					printer.print_time_remaining = pd.get("time_remaining", 0.0)
					printer.print_progress = pd.get("print_progress", 0.0)
					printer._set_state(Printer.State.PRINTING)
			else:
				# Order-driven print restore (existing path)
				if is_instance_valid(product_db) and is_instance_valid(mat_db):
					var product = (product_db as ProductDatabase).get_product(pd.get("current_product_id", ""))
					var material = (mat_db as MaterialDatabase).get_material(pd.get("current_material_id", "pla"))
					if product and material:
						printer.current_product = product
						printer.current_material = material
						printer.print_time_total = pd.get("time_total", 0.0)
						printer.print_time_remaining = pd.get("time_remaining", 0.0)
						printer.print_progress = pd.get("print_progress", 0.0)
						printer._set_state(Printer.State.PRINTING)

		elif state_str == "DONE" and is_instance_valid(product_db):
			var product = (product_db as ProductDatabase).get_product(pd.get("current_product_id", ""))
			if product:
				printer.current_product = product
				printer.print_progress = 1.0
				printer._set_state(Printer.State.DONE)

		elif state_str == "WAITING_PICKUP":
			if is_instance_valid(product_db):
				var product = (product_db as ProductDatabase).get_product(pd.get("current_product_id", ""))
				if product:
					printer.current_product = product
			if is_instance_valid(mat_db):
				var material = (mat_db as MaterialDatabase).get_material(pd.get("current_material_id", "pla"))
				if material:
					printer.current_material = material
			printer.print_progress = 1.0
			printer.plate_dirty = pd.get("plate_dirty", true)  # Default true for restored pickup state
			printer._set_state(Printer.State.WAITING_PICKUP)

		elif state_str == "NEEDS_CLEANING":
			if is_instance_valid(product_db):
				var product = (product_db as ProductDatabase).get_product(pd.get("current_product_id", ""))
				if product:
					printer.current_product = product
			printer.plate_dirty = pd.get("plate_dirty", true)  # Default true for restored cleaning state
			printer._set_state(Printer.State.NEEDS_CLEANING)
