class_name UnlockSimulator
extends Node

# Load the actual game resources
var unlock_collection: UnlockDataCollection = load("res://resources/UnlockData.tres")
var inventory_resource: InventoryResource = load("res://resources/InventoryData.tres")

# Results tracking
var all_results: Array[Dictionary] = []
var results_mutex: Mutex = Mutex.new()

# Manual thread pool
var num_threads: int = 12 # Increase this for more CPU usage
var threads: Array[Thread] = []
var task_queue: Array[Dictionary] = []
var queue_mutex: Mutex = Mutex.new()
var completed_count: int = 0
var completed_mutex: Mutex = Mutex.new()
var active_threads: int = 0
var threads_done: bool = false

var start_time: int = 0
var total_combinations: int = 0
var last_progress_time: int = 0
var monitoring_active: bool = false

func _ready():
	print("=== Unlock Simulator Started ===")
	var cpu_count = OS.get_processor_count()
	print("CPU cores detected: %d" % cpu_count)
	print("Creating %d worker threads (adjust num_threads variable for more/less)" % num_threads)
	run_comprehensive_test()

func _process(_delta):
	if monitoring_active:
		# Only update progress once per second
		var current_time = Time.get_ticks_msec()
		if current_time - last_progress_time >= 1000:
			last_progress_time = current_time
			update_progress()

func update_progress():
	"""Update progress display"""
	var current_count = 0
	completed_mutex.lock()
	current_count = completed_count
	completed_mutex.unlock()
	
	# Check if all work is complete
	if current_count >= total_combinations:
		monitoring_active = false
		finish_processing()
		return
	
	var percent = float(current_count) / total_combinations * 100.0
	var elapsed = (Time.get_ticks_msec() - start_time) / 1000.0
	var rate = current_count / elapsed if elapsed > 0 else 0
	var eta_seconds = (total_combinations - current_count) / rate if rate > 0 else 0
	
	# Format ETA
	var eta_str = ""
	if eta_seconds > 0:
		var eta_minutes = int(eta_seconds) / 60
		var eta_secs = int(eta_seconds) % 60
		if eta_minutes > 0:
			eta_str = "%dm %ds" % [eta_minutes, eta_secs]
		else:
			eta_str = "%ds" % eta_secs
	else:
		eta_str = "calculating..."
	
	print("Progress: %.1f%% (%d/%d) - %.1f combos/sec - ETA: %s" % [
		percent, current_count, total_combinations, rate, eta_str
	])

func worker_thread(thread_id: int):
	"""Worker thread function that pulls tasks from the queue"""
	while true:
		# Get next task from queue
		var task_data = null
		queue_mutex.lock()
		if task_queue.size() > 0:
			task_data = task_queue.pop_front()
		queue_mutex.unlock()
		
		# If no more tasks, exit
		if task_data == null:
			break
		
		# Process the task
		var result = simulate_rank_combination_pure(task_data.combo, task_data.unlock_data, 100000)
		
		# Store result
		results_mutex.lock()
		all_results.append(result)
		results_mutex.unlock()
		
		# Increment counter
		completed_mutex.lock()
		completed_count += 1
		completed_mutex.unlock()

func simulate_rank_combination_pure(rank_targets: Dictionary, unlock_data_array: Array, max_ticks: int) -> Dictionary:
	"""Pure simulation function that can run in parallel"""
	var currency: float = 0.0
	var stock: float = 0.0
	
	# Create unlock instances from serialized data
	var unlocks: Array = []
	for unlock_data in unlock_data_array:
		var unlock = UnlockDataResource.new()
		unlock.unlock_id = unlock_data.unlock_id
		unlock.unlock_name = unlock_data.unlock_name
		unlock.base_cost = unlock_data.base_cost
		unlock.is_scaling = unlock_data.is_scaling
		unlock.max_rank = unlock_data.max_rank
		unlock.cost_scaling_multiplier = unlock_data.cost_scaling_multiplier
		unlock.effect_scaling_multiplier = unlock_data.effect_scaling_multiplier
		unlock.base_modifiers = unlock_data.base_modifiers.duplicate()
		unlock.is_unlocked = false
		unlock.current_rank = 0
		unlocks.append(unlock)
	
	var ticks = 0
	var purchases: Array[Dictionary] = []
	var current_ranks = {}
	
	# Initialize current ranks
	for unlock_id in rank_targets.keys():
		current_ranks[unlock_id] = 0
	
	# Helper to check if all targets reached
	var all_targets_reached = func() -> bool:
		for unlock_id in rank_targets.keys():
			if current_ranks[unlock_id] < rank_targets[unlock_id]:
				return false
		return true
	
	# Calculate modifiers helper
	var calc_modifiers = func() -> Dictionary:
		var mods = {
			"sale_price_modifier": 1.0,
			"speed_modifier": 1.0,
			"efficiency_modifier": 1.0,
			"wood_respawn_modifier": 1.0,
			"wood_per_click_modifier": 1.0,
			"purchase_rate_modifier": 1.0,
		}
		for unlock in unlocks:
			if unlock.is_unlocked:
				var unlock_modifiers = unlock.get_current_modifiers()
				for key in unlock_modifiers.keys():
					if mods.has(key):
						mods[key] *= unlock_modifiers[key]
		return mods
	
	var modifiers = calc_modifiers.call()
	
	while ticks < max_ticks and currency < 100000.0:
		# Try to buy the cheapest available unlock that hasn't reached its target
		var cheapest_unlock_id = null
		var cheapest_cost = INF
		var cheapest_unlock_obj = null
		
		for unlock_id in rank_targets.keys():
			if current_ranks[unlock_id] < rank_targets[unlock_id]:
				# Find the unlock object
				var unlock = null
				for u in unlocks:
					if u.unlock_id == unlock_id:
						unlock = u
						break
				
				if unlock and unlock.can_rank_up():
					var cost = unlock.get_next_cost()
					if cost < cheapest_cost and currency >= cost:
						cheapest_cost = cost
						cheapest_unlock_id = unlock_id
						cheapest_unlock_obj = unlock
		
		# Purchase the cheapest unlock if found
		if cheapest_unlock_obj != null:
			currency -= cheapest_cost
			cheapest_unlock_obj.unlock()
			current_ranks[cheapest_unlock_id] += 1
			
			# Recalculate modifiers
			modifiers = calc_modifiers.call()
			
			purchases.append({
				"tick": ticks,
				"unlock_id": cheapest_unlock_id,
				"unlock_name": cheapest_unlock_obj.unlock_name,
				"rank": cheapest_unlock_obj.current_rank,
				"currency": currency,
				"cost_paid": cheapest_cost,
				"modifiers_after": modifiers.duplicate()
			})
		
		# Simulate one tick
		var items_per_tick = Global.cost_per_whittle * modifiers.get("efficiency_modifier", 1.0)
		stock += items_per_tick
		
		var demand = Global.base_purchase_rate * modifiers.get("purchase_rate_modifier", 1.0)
		var items_sold = min(stock, demand)
		stock -= items_sold
		
		var price_per_item = Global.base_sale_price * modifiers.get("sale_price_modifier", 1.0)
		var revenue = items_sold * price_per_item
		currency += revenue
		
		ticks += 1
		
		# Check if we've reached target and 10K
		if all_targets_reached.call() and currency >= 100000.0:
			break
	
	var success = currency >= 100000.0
	
	return {
		"rank_targets": rank_targets,
		"success": success,
		"ticks": ticks if success else -1,
		"final_currency": currency,
		"purchases": purchases,
		"time_formatted": format_time(ticks) if success else "Failed"
	}

func format_time(ticks: int) -> String:
	var seconds = ticks
	var minutes = seconds / 60
	var hours = minutes / 60
	
	if hours > 0:
		return "%dh %dm %ds" % [hours, minutes % 60, seconds % 60]
	elif minutes > 0:
		return "%dm %ds" % [minutes, seconds % 60]
	else:
		return "%ds" % seconds

func generate_rank_combinations(max_ranks_per_unlock: int = 10) -> Array[Dictionary]:
	"""Generate all combinations of ranks for the first 4 unlocks"""
	var combinations: Array[Dictionary] = []
	
	# Get first 4 unlock IDs
	var unlock_ids = []
	for i in range(min(4, unlock_collection.unlocks.size())):
		unlock_ids.append(unlock_collection.unlocks[i].unlock_id)
	
	print("Generating combinations for unlocks: ", unlock_ids)
	
	# Generate all combinations (0 to max_ranks for each unlock)
	for rank1 in range(max_ranks_per_unlock + 1):
		for rank2 in range(max_ranks_per_unlock + 1):
			for rank3 in range(max_ranks_per_unlock + 1):
				for rank4 in range(max_ranks_per_unlock + 1):
					# Skip the all-zeros case
					if rank1 == 0 and rank2 == 0 and rank3 == 0 and rank4 == 0:
						continue
					
					var combination = {}
					if rank1 > 0:
						combination[unlock_ids[0]] = rank1
					if rank2 > 0:
						combination[unlock_ids[1]] = rank2
					if rank3 > 0:
						combination[unlock_ids[2]] = rank3
					if rank4 > 0:
						combination[unlock_ids[3]] = rank4
					
					combinations.append(combination)
	
	return combinations

func serialize_unlock_data() -> Array:
	"""Convert unlock collection to serializable data for threads"""
	var unlock_data = []
	for unlock in unlock_collection.unlocks:
		unlock_data.append({
			"unlock_id": unlock.unlock_id,
			"unlock_name": unlock.unlock_name,
			"base_cost": unlock.base_cost,
			"is_scaling": unlock.is_scaling,
			"max_rank": unlock.max_rank,
			"cost_scaling_multiplier": unlock.cost_scaling_multiplier,
			"effect_scaling_multiplier": unlock.effect_scaling_multiplier,
			"base_modifiers": unlock.base_modifiers.duplicate()
		})
	return unlock_data

func run_comprehensive_test(max_ranks: int = 10):
	"""Test all combinations of ranks up to max_ranks for each unlock"""
	print("\n=== Available Unlocks ===")
	for unlock in unlock_collection.unlocks:
		print("ID: %d | %s | Base Cost: %d | Scaling: %s" % [
			unlock.unlock_id,
			unlock.unlock_name,
			unlock.base_cost,
			"Yes" if unlock.is_scaling else "No"
		])
		print("  Modifiers: ", unlock.base_modifiers)
	
	print("\n=== Global Constants ===")
	print("Base Sale Price: %s" % Global.base_sale_price)
	print("Base Purchase Rate: %s" % Global.base_purchase_rate)
	print("Cost Per Whittle: %s" % Global.cost_per_whittle)
	
	# Generate all combinations
	var combinations = generate_rank_combinations(max_ranks)
	total_combinations = combinations.size()
	print("\n=== Testing %d Combinations ===" % total_combinations)
	
	# Serialize unlock data for threads
	var unlock_data = serialize_unlock_data()
	
	# Fill task queue
	task_queue.clear()
	for combo in combinations:
		task_queue.append({
			"combo": combo,
			"unlock_data": unlock_data
		})
	
	# Reset counters
	completed_count = 0
	all_results.clear()
	threads_done = false
	start_time = Time.get_ticks_msec()
	last_progress_time = start_time
	monitoring_active = true
	
	# Create and start threads
	print("Starting %d worker threads..." % num_threads)
	for i in range(num_threads):
		var thread = Thread.new()
		thread.start(worker_thread.bind(i))
		threads.append(thread)
	
	print("All threads started, processing...")

func finish_processing():
	"""Called when all processing is complete"""
	print("\nAll combinations complete! Waiting for threads to finish...")
	
	# Wait for all threads to finish
	for thread in threads:
		thread.wait_to_finish()
	threads.clear()
	threads_done = true
	
	print("All threads finished. Processing results...")
	
	var total_time = (Time.get_ticks_msec() - start_time) / 1000.0
	
	# Print results
	print("\n=== RESULTS ===")
	print("Total time: %.1f seconds" % total_time)
	print("Total combinations tested: %d" % all_results.size())
	
	var successful = all_results.filter(func(r): return r.success)
	print("Successful strategies: %d" % successful.size())
	
	if successful.size() > 0:
		# Sort by ticks (fastest first)
		successful.sort_custom(func(a, b): return a.ticks < b.ticks)
		
		print("\n=== TOP 10 FASTEST STRATEGIES ===")
		for i in range(min(10, successful.size())):
			var result = successful[i]
			print("\n#%d: %s (%d ticks)" % [i + 1, result.time_formatted, result.ticks])
			
			# Format ranks with unlock names
			var rank_display = []
			for unlock_id in result.rank_targets.keys():
				var unlock_name = get_unlock_name_by_id(unlock_id)
				var ranks = result.rank_targets[unlock_id]
				rank_display.append("%s: %d" % [unlock_name, ranks])
			print("Target Ranks: %s" % ", ".join(rank_display))
			
			# Show purchase order
			print("Purchase Order:")
			for purchase in result.purchases:
				var key_mods = ""
				if purchase.has("modifiers_after"):
					var mods = purchase.modifiers_after
					key_mods = " [Sale:%.2fx Eff:%.2fx Demand:%.2fx]" % [
						mods.get("sale_price_modifier", 1.0),
						mods.get("efficiency_modifier", 1.0),
						mods.get("purchase_rate_modifier", 1.0)
					]
				var cost_info = ""
				if purchase.has("cost_paid"):
					cost_info = " (paid %d)" % purchase.cost_paid
				print("  @%s: %s -> Rank %d%s - %.0f currency%s" % [
					format_time(purchase.tick),
					purchase.unlock_name,
					purchase.rank,
					cost_info,
					purchase.currency,
					key_mods
				])
	else:
		print("\nNo successful strategies found!")

func get_unlock_name_by_id(unlock_id: int) -> String:
	"""Helper function to get unlock name by ID"""
	for unlock in unlock_collection.unlocks:
		if unlock.unlock_id == unlock_id:
			return unlock.unlock_name
	return "Unknown"

func _exit_tree():
	# Clean up threads
	monitoring_active = false
	for thread in threads:
		if thread.is_alive():
			thread.wait_to_finish()