class_name MapGenerationClass
extends Node

# Map settings
var map_width: int = Global.map_width
var map_height: int = Global.map_height

# Path generation settings
@export var num_horizontal_paths: int = 3
@export var num_vertical_paths: int = 2
@export var path_wander_strength: float = 0.3 # How much paths can deviate (0-1)
@export var path_smoothing_passes: int = 2 # Number of smoothing iterations
@export var branch_probability: float = 0.6 # Chance of creating branches (0-1)
@export var max_branches_per_path: int = 3 # Maximum branches per main path
@export var dead_end_probability: float = 0.3 # Chance for branches to be dead ends (0-1)
@export var min_branch_length_ratio: float = 0.2 # Minimum branch length as ratio of main path
@export var max_branch_length_ratio: float = 0.4 # Maximum branch length as ratio of main path

# Camp generation settings
@export var camp_size: int = 2 # Size of the camp (camp_size x camp_size)
@export var camp_placement_attempts: int = 100 # Max attempts to place the camp
@export var camp_buffer_zone: int = 1 # Minimum distance from camp to other features

# Water generation settings
@export var num_water_bodies: int = 5 # Number of water bodies to generate
@export var min_water_size: int = 15 # Minimum radius of water bodies
@export var max_water_size: int = 34 # Maximum radius of water bodies
@export var water_placement_attempts: int = 50 # Max attempts to place each water body

# Image export settings
@export var tile_size: int = 8 # Size of each tile in pixels (square)
@export var export_image: bool = true # Whether to export image
@export var image_filename: String = "generated_map.png" # Output filename

var noise: FastNoiseLite
var map: Array = []
var map_data: Array = []
var path_data: Array = [] # Separate array to track paths
var water_data: Array = [] # Separate array to track water bodies
var camp_data: Array = [] # Separate array to track camp

# Camp position storage
var camp_position: Vector2 = Vector2(-1, -1) # Top-left corner of the camp
var camp_center: Vector2 = Vector2(-1, -1) # Center of the camp

# New data structures for enhanced path generation
var path_segments: Array = [] # Store all path segments for better branching
var path_id_counter: int = 0

func _ready():
	generate_map()
	generate_camp()
	generate_paths()
	generate_water_bodies()
	BiomeGenerationClass.generate_environment_maps(map_width, map_height)
	generate_final_map_data()
	
	
	if export_image:
		export_map_as_image()

func generate_final_map_data():
	var objects_before = Performance.get_monitor(Performance.OBJECT_COUNT)

	for y in range(map_height):
		for x in range(map_width):
			MapPopulationClass.generate_cell_with_distribution(x, y, map_data[y][x], is_path_at(x, y), is_water_at(x, y), is_camp_at(x, y))
	
	Log.pr(camp_center)
	Global.spawn_point = Vector3(camp_position.x * 2, 0, camp_position.y * 2) # Set spawn point to camp center
	Log.pr(Global.spawn_point)

	# Check immediately after
	await get_tree().process_frame
	var objects_after = Performance.get_monitor(Performance.OBJECT_COUNT)
	Log.pr("Objects created in generate_final_map_data: ", objects_after - objects_before)
			

func generate_map():
	# Initialize noise
	noise = FastNoiseLite.new()
	noise.seed = randi()
	noise.frequency = 0.01
	noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
	
	# Fractal settings for organic paths
	noise.fractal_type = FastNoiseLite.FRACTAL_RIDGED
	noise.fractal_octaves = 2
	noise.fractal_lacunarity = 4.0
	noise.fractal_gain = 0.2
	noise.fractal_weighted_strength = 1
	noise.fractal_ping_pong_strength = 2.0
	
	# Domain warp for natural winding
	noise.domain_warp_enabled = true
	noise.domain_warp_type = FastNoiseLite.DOMAIN_WARP_BASIC_GRID
	noise.domain_warp_amplitude = 20
	noise.domain_warp_frequency = 0.01

	# Generate map data
	map_data.resize(map_height)
	path_data.resize(map_height)
	water_data.resize(map_height)
	camp_data.resize(map_height)

	for y in range(map_height):
		map_data[y] = []
		path_data[y] = []
		water_data[y] = []
		camp_data[y] = []
		map_data[y].resize(map_width)
		path_data[y].resize(map_width)
		water_data[y].resize(map_width)
		camp_data[y].resize(map_width)
		for x in range(map_width):
			# Get noise value (-1 to 1) and normalize to (0 to 1)
			var noise_value = noise.get_noise_2d(x, y)
			var normalized_value = (noise_value + 1.0) / 2.0
			map_data[y][x] = round(normalized_value * 10.0) / 10.0
			path_data[y][x] = false # Initialize path data
			water_data[y][x] = false # Initialize water data
			camp_data[y][x] = false # Initialize camp data

func generate_camp():
	print("Generating camp...")
	
	var attempts = 0
	var placed = false
	
	while attempts < camp_placement_attempts and not placed:
		# Random position with margin to ensure camp fits within map bounds
		var margin = camp_size + camp_buffer_zone
		var camp_x = randi_range(margin, map_width - margin - camp_size)
		var camp_y = randi_range(margin, map_height - margin - camp_size)
		
		# Check if this location is valid for camp placement
		if is_valid_camp_location(camp_x, camp_y):
			place_camp(camp_x, camp_y)
			placed = true
			print("Camp placed at (", camp_x, ",", camp_y, ") with size ", camp_size, "x", camp_size)
		
		attempts += 1
	
	if not placed:
		print("Could not place camp after ", camp_placement_attempts, " attempts")
		# Fallback: place camp in the center of the map
		var fallback_x = (map_width - camp_size) / 2
		var fallback_y = (map_height - camp_size) / 2
		place_camp(fallback_x, fallback_y)
		print("Camp placed at fallback location (", fallback_x, ",", fallback_y, ")")

func is_valid_camp_location(camp_x: int, camp_y: int) -> bool:
	# Check if the camp area and buffer zone are clear
	var check_size = camp_size + (camp_buffer_zone * 2)
	var start_x = camp_x - camp_buffer_zone
	var start_y = camp_y - camp_buffer_zone
	
	for y in range(start_y, start_y + check_size):
		for x in range(start_x, start_x + check_size):
			if x >= 0 and x < map_width and y >= 0 and y < map_height:
				# For now, just check if we're within map bounds
				# Later we'll prevent paths and water from being placed here
				continue
			else:
				# Outside map bounds
				return false
	
	return true

func place_camp(camp_x: int, camp_y: int):
	# Store camp position
	camp_position = Vector2(camp_x, camp_y)
	camp_center = Vector2(camp_x + camp_size / 2.0, camp_y + camp_size / 2.0)

	Log.pr("Placing camp at position: ", camp_position, " with center: ", camp_center)
	
	# Mark camp area in camp_data
	for y in range(camp_y, camp_y + camp_size):
		for x in range(camp_x, camp_x + camp_size):
			if x >= 0 and x < map_width and y >= 0 and y < map_height:
				camp_data[y][x] = true

func generate_paths():
	print("Generating natural paths with branching...")
	path_segments.clear()
	path_id_counter = 0
	
	# Generate main organic paths
	for i in range(num_horizontal_paths):
		generate_main_path(i, "horizontal")
	
	for i in range(num_vertical_paths):
		generate_main_path(i, "vertical")
	
	# Generate branches for existing paths
	generate_path_branches()
	
	# Smooth and connect paths
	smooth_paths()

func generate_main_path(path_index: int, direction: String):
	var path_points = []
	
	# Set up start and end points based on direction
	var start_point: Vector2
	var end_point: Vector2
	
	if direction == "horizontal":
		# Horizontal paths go from left edge to right edge
		var y_pos = (map_height / (num_horizontal_paths + 1)) * (path_index + 1)
		y_pos += randi_range(-map_height / 8, map_height / 8) # Add some vertical variation
		start_point = Vector2(0, clamp(y_pos, 5, map_height - 5))
		end_point = Vector2(map_width - 1, clamp(y_pos + randi_range(-map_height / 4, map_height / 4), 5, map_height - 5))
	else:
		# Vertical paths go from top edge to bottom edge
		var x_pos = (map_width / (num_vertical_paths + 1)) * (path_index + 1)
		x_pos += randi_range(-map_width / 8, map_width / 8) # Add some horizontal variation
		start_point = Vector2(clamp(x_pos, 5, map_width - 5), 0)
		end_point = Vector2(clamp(x_pos + randi_range(-map_width / 4, map_width / 4), 5, map_width - 5), map_height - 1)
	
	# Generate control points for smooth curves
	var control_points = [start_point]
	
	# Add several intermediate control points for natural curves
	var num_control_points = randi_range(3, 6)
	for i in range(1, num_control_points):
		var t = float(i) / float(num_control_points)
		
		# Interpolate between start and end
		var base_point = start_point.lerp(end_point, t)
		
		# Add random offset for natural wandering
		var max_offset = min(map_width, map_height) * 0.3 * path_wander_strength
		var offset = Vector2(
			randf_range(-max_offset, max_offset),
			randf_range(-max_offset, max_offset)
		)
		
		var control_point = base_point + offset
		control_point.x = clamp(control_point.x, 0, map_width - 1)
		control_point.y = clamp(control_point.y, 0, map_height - 1)
		
		control_points.append(control_point)
	
	control_points.append(end_point)
	
	# Create main path and store segment info
	var path_id = path_id_counter
	path_id_counter += 1
	
	var segment_info = {
		"id": path_id,
		"type": "main",
		"direction": direction,
		"control_points": control_points,
		"parent_id": - 1,
		"branch_points": [] # Points where branches can spawn
	}
	
	path_segments.append(segment_info)
	create_spline_path(control_points, path_id, true) # true = is_main_path

func generate_path_branches():
	print("Generating path branches...")
	
	# Create branches for each main path
	for segment in path_segments:
		if segment.type == "main":
			generate_branches_for_segment(segment)

func generate_branches_for_segment(main_segment: Dictionary):
	var num_branches = 0
	var max_attempts = 5
	
	# Try to create branches up to the maximum allowed
	while num_branches < max_branches_per_path and max_attempts > 0:
		max_attempts -= 1
		
		if randf() < branch_probability:
			create_branch_from_segment(main_segment, num_branches)
			num_branches += 1

func create_branch_from_segment(parent_segment: Dictionary, branch_index: int):
	# Pick a random point along the parent path to branch from
	var branch_start_t = randf_range(0.2, 0.8) # Don't branch too close to ends
	var branch_start_point = evaluate_catmull_rom_spline(parent_segment.control_points, branch_start_t)
	
	# Determine branch direction based on parent direction and some randomness
	var branch_direction = get_branch_direction(parent_segment.direction, branch_start_point)
	
	# Calculate branch length
	var main_path_length = estimate_path_length(parent_segment.control_points)
	var branch_length_ratio = randf_range(min_branch_length_ratio, max_branch_length_ratio)
	var target_branch_length = main_path_length * branch_length_ratio
	
	# Determine if this should be a dead end
	var is_dead_end = randf() < dead_end_probability
	
	# Generate branch end point
	var branch_end_point: Vector2
	if is_dead_end:
		# Dead end - create a point that doesn't reach map edge
		branch_end_point = create_dead_end_point(branch_start_point, branch_direction, target_branch_length)
	else:
		# Through branch - try to reach map edge or connect to another path
		branch_end_point = create_through_branch_point(branch_start_point, branch_direction, target_branch_length)
	
	# Generate branch control points
	var branch_control_points = generate_branch_control_points(branch_start_point, branch_end_point, branch_direction)
	
	# Create the branch
	var branch_id = path_id_counter
	path_id_counter += 1
	
	var branch_segment = {
		"id": branch_id,
		"type": "branch",
		"direction": branch_direction,
		"control_points": branch_control_points,
		"parent_id": parent_segment.id,
		"is_dead_end": is_dead_end
	}
	
	path_segments.append(branch_segment)
	create_spline_path(branch_control_points, branch_id, false) # false = not main path

func get_branch_direction(parent_direction: String, branch_point: Vector2) -> String:
	# Determine branch direction based on parent and position
	var directions = []
	
	if parent_direction == "horizontal":
		directions = ["north", "south"]
		# Add some bias based on position
		if branch_point.y < map_height * 0.3:
			directions.append("south") # Bias toward south if in upper area
		elif branch_point.y > map_height * 0.7:
			directions.append("north") # Bias toward north if in lower area
	else: # vertical
		directions = ["east", "west"]
		# Add some bias based on position
		if branch_point.x < map_width * 0.3:
			directions.append("east") # Bias toward east if in left area
		elif branch_point.x > map_width * 0.7:
			directions.append("west") # Bias toward west if in right area
	
	return directions[randi() % directions.size()]

func create_dead_end_point(start_point: Vector2, direction: String, target_length: float) -> Vector2:
	# Create a point for a dead end that doesn't reach the map edge
	var direction_vector = get_direction_vector(direction)
	var max_distance = target_length * randf_range(0.5, 0.9) # Dead ends are shorter
	
	# Add some randomness to the direction
	var angle_variation = randf_range(-PI / 4, PI / 4) # ±45 degrees
	direction_vector = direction_vector.rotated(angle_variation)
	
	var end_point = start_point + direction_vector * max_distance
	
	# Ensure the end point stays within map bounds with some margin
	var margin = 20
	end_point.x = clamp(end_point.x, margin, map_width - margin)
	end_point.y = clamp(end_point.y, margin, map_height - margin)
	
	return end_point

func create_through_branch_point(start_point: Vector2, direction: String, target_length: float) -> Vector2:
	# Create a point that tries to reach toward a map edge
	var direction_vector = get_direction_vector(direction)
	
	# Add some wandering to make it more natural
	var angle_variation = randf_range(-PI / 6, PI / 6) # ±30 degrees
	direction_vector = direction_vector.rotated(angle_variation)
	
	var end_point = start_point + direction_vector * target_length
	
	# Try to reach toward the appropriate map edge
	match direction:
		"north":
			end_point.y = max(5, end_point.y)
		"south":
			end_point.y = min(map_height - 5, end_point.y)
		"east":
			end_point.x = min(map_width - 5, end_point.x)
		"west":
			end_point.x = max(5, end_point.x)
	
	# Clamp to map bounds
	end_point.x = clamp(end_point.x, 5, map_width - 5)
	end_point.y = clamp(end_point.y, 5, map_height - 5)
	
	return end_point

func get_direction_vector(direction: String) -> Vector2:
	match direction:
		"north":
			return Vector2(0, -1)
		"south":
			return Vector2(0, 1)
		"east":
			return Vector2(1, 0)
		"west":
			return Vector2(-1, 0)
		_:
			return Vector2(0, 1) # Default to south

func generate_branch_control_points(start_point: Vector2, end_point: Vector2, direction: String) -> Array:
	var control_points = [start_point]
	
	# Add fewer control points for branches to make them simpler
	var num_control_points = randi_range(2, 4)
	
	for i in range(1, num_control_points):
		var t = float(i) / float(num_control_points)
		var base_point = start_point.lerp(end_point, t)
		
		# Less wandering for branches
		var max_offset = min(map_width, map_height) * 0.15 * path_wander_strength
		var offset = Vector2(
			randf_range(-max_offset, max_offset),
			randf_range(-max_offset, max_offset)
		)
		
		var control_point = base_point + offset
		control_point.x = clamp(control_point.x, 0, map_width - 1)
		control_point.y = clamp(control_point.y, 0, map_height - 1)
		
		control_points.append(control_point)
	
	control_points.append(end_point)
	return control_points

func estimate_path_length(control_points: Array) -> float:
	# Rough estimate of path length for branch sizing
	var total_length = 0.0
	for i in range(control_points.size() - 1):
		total_length += control_points[i].distance_to(control_points[i + 1])
	return total_length

func create_spline_path(control_points: Array, path_id: int, is_main_path: bool = false):
	# Create a smooth path using Catmull-Rom spline interpolation
	var path_resolution = max(map_width, map_height) * 2 # High resolution for smooth curves
	
	# Reduce resolution for branches to make them less dense
	if not is_main_path:
		path_resolution = int(path_resolution * 0.7)
	
	for i in range(path_resolution):
		var t = float(i) / float(path_resolution - 1)
		var point = evaluate_catmull_rom_spline(control_points, t)
		
		# Place path segments along the spline
		var x = int(round(point.x))
		var y = int(round(point.y))
		
		if x >= 0 and x < map_width and y >= 0 and y < map_height:
			place_organic_path_segment(x, y, path_id, is_main_path)

func evaluate_catmull_rom_spline(points: Array, t: float) -> Vector2:
	var n = points.size()
	if n < 2:
		return points[0] if n > 0 else Vector2.ZERO
	
	# Handle edge cases
	if t <= 0.0:
		return points[0]
	if t >= 1.0:
		return points[n - 1]
	
	# Find the segment
	var segment_length = 1.0 / float(n - 1)
	var segment_index = int(t / segment_length)
	segment_index = clamp(segment_index, 0, n - 2)
	
	# Local t within the segment
	var local_t = (t - segment_index * segment_length) / segment_length
	
	# Get control points (with clamping for edge segments)
	var p0 = points[max(0, segment_index - 1)]
	var p1 = points[segment_index]
	var p2 = points[min(n - 1, segment_index + 1)]
	var p3 = points[min(n - 1, segment_index + 2)]
	
	# Catmull-Rom spline interpolation
	var t2 = local_t * local_t
	var t3 = t2 * local_t
	
	var result = Vector2.ZERO
	result += p0 * (-0.5 * t3 + t2 - 0.5 * local_t)
	result += p1 * (1.5 * t3 - 2.5 * t2 + 1.0)
	result += p2 * (-1.5 * t3 + 2.0 * t2 + 0.5 * local_t)
	result += p3 * (0.5 * t3 - 0.5 * t2)
	
	return result

func place_organic_path_segment(x: int, y: int, path_id: int, is_main_path: bool = false):
	# Don't place paths in camp area
	if is_camp_at(x, y):
		return
	
	# Base path placement - always place the center tile
	path_data[y][x] = true
	
	if is_main_path:
		# Main paths can be wider (2-3 tiles)
		var width_noise = FastNoiseLite.new()
		width_noise.seed = randi() + path_id * 5000
		width_noise.frequency = 0.1
		width_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
		
		# Add organic width variation for main paths only
		var width_value = width_noise.get_noise_2d(x, y)
		var base_width = 1 + int((width_value + 1.0) * 1.0) # Width 1-3 tiles for main paths
		var path_width = max(1, base_width)
		
		# Place path tiles in organic pattern around the center for main paths
		for dy in range(-path_width, path_width + 1):
			for dx in range(-path_width, path_width + 1):
				var distance = sqrt(dx * dx + dy * dy)
				if distance <= path_width:
					var nx = x + dx
					var ny = y + dy
					
					if nx >= 0 and nx < map_width and ny >= 0 and ny < map_height:
						# Don't place paths in camp area
						if is_camp_at(nx, ny):
							continue
						
						# Use noise to create organic edges
						var edge_noise = width_noise.get_noise_2d(nx, ny)
						var edge_chance = 1.0 - (distance / float(path_width))
						edge_chance += edge_noise * 0.3
						
						if edge_chance > 0.4:
							path_data[ny][nx] = true
	# For branches (is_main_path = false), only the center tile is placed above

func smooth_paths():
	# Apply smoothing passes to make paths more natural
	for pass_num in range(path_smoothing_passes):
		var new_path_data = path_data.duplicate(true)
		
		for y in range(1, map_height - 1):
			for x in range(1, map_width - 1):
				if path_data[y][x]:
					# Check neighbors and potentially expand path
					var neighbor_count = 0
					for dy in range(-1, 2):
						for dx in range(-1, 2):
							if dx == 0 and dy == 0:
								continue
							if path_data[y + dy][x + dx]:
								neighbor_count += 1
					
					# If isolated path point, try to connect it
					if neighbor_count < 2 and randf() > 0.5:
						# Find nearest path point and create connection
						for radius in range(1, 4):
							var connected = false
							for dy in range(-radius, radius + 1):
								for dx in range(-radius, radius + 1):
									var ny = y + dy
									var nx = x + dx
									if ny >= 0 and ny < map_height and nx >= 0 and nx < map_width:
										if path_data[ny][nx] and (abs(dx) + abs(dy)) <= radius:
											# Create connection
											var steps = max(abs(dx), abs(dy))
											for step in range(steps + 1):
												var interp_x = x + int(float(dx * step) / float(steps))
												var interp_y = y + int(float(dy * step) / float(steps))
												if interp_x >= 0 and interp_x < map_width and interp_y >= 0 and interp_y < map_height:
													# Don't place paths in camp area
													if not is_camp_at(interp_x, interp_y):
														new_path_data[interp_y][interp_x] = true
											connected = true
											break
								if connected:
									break
							if connected:
								break
		
		path_data = new_path_data

func generate_water_bodies():
	print("Generating water bodies...")
	
	for i in range(num_water_bodies):
		generate_single_water_body(i)

func generate_single_water_body(body_index: int):
	var attempts = 0
	var placed = false
	
	while attempts < water_placement_attempts and not placed:
		# Random center position
		var center_x = randi_range(max_water_size, map_width - max_water_size)
		var center_y = randi_range(max_water_size, map_height - max_water_size)
		
		# Random size within configured range
		var water_radius = randi_range(min_water_size, max_water_size) - attempts
		
		# Check if this location is valid (no paths or camp in the area)
		if is_valid_water_location(center_x, center_y, water_radius):
			place_water_body(center_x, center_y, water_radius, body_index)
			placed = true
			print("Water body ", body_index + 1, " placed at (", center_x, ",", center_y, ") with radius ", water_radius)
		
		attempts += 1
	
	if not placed:
		print("Could not place water body ", body_index + 1, " after ", water_placement_attempts, " attempts")

func is_valid_water_location(center_x: int, center_y: int, radius: int) -> bool:
	# Check if any paths or camp would be intersected by this water body
	# Use a slightly larger radius for safety buffer
	var safety_buffer = 2
	var check_radius = radius + safety_buffer
	
	for y in range(center_y - check_radius, center_y + check_radius + 1):
		for x in range(center_x - check_radius, center_x + check_radius + 1):
			if x >= 0 and x < map_width and y >= 0 and y < map_height:
				# Check if this position has a path
				if path_data[y][x]:
					var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
					if distance <= check_radius:
						return false
				
				# Check if this position has camp
				if camp_data[y][x]:
					var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
					if distance <= check_radius:
						return false
				
				# Also check if there's already water here (prevent overlap)
				if water_data[y][x]:
					var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
					if distance <= check_radius:
						return false
	
	return true

func place_water_body(center_x: int, center_y: int, radius: int, body_index: int):
	# Simple approach: start with basic circle, then use noise to make edges organic
	var water_noise = FastNoiseLite.new()
	water_noise.seed = randi() + body_index * 3000
	water_noise.frequency = 0.1
	water_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
	
	# Place water in roughly circular area with noise-modified edges
	for y in range(center_y - radius - 2, center_y + radius + 3):
		for x in range(center_x - radius - 2, center_x + radius + 3):
			if x >= 0 and x < map_width and y >= 0 and y < map_height:
				if path_data[y][x] or camp_data[y][x]: # Don't place water on paths or camp
					continue
				
				# Calculate distance from center
				var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
				
				# Use noise to modify the effective radius at this position
				var noise_value = water_noise.get_noise_2d(x, y)
				var radius_modifier = noise_value * radius * 0.4 # 40% variation
				var effective_radius = radius + radius_modifier
				
				# Place water if within the noise-modified radius
				if distance <= effective_radius:
					water_data[y][x] = true

func print_visual_map_to_console():
	print("")
	print("==================================================")
	print("VISUAL MAP WITH BRANCHING PATHS, DEAD ENDS, AND CAMP")
	print("==================================================")
	print("Path Statistics:")
	print("- Total path segments: ", path_segments.size())
	var main_paths = path_segments.filter(func(seg): return seg.type == "main")
	var branches = path_segments.filter(func(seg): return seg.type == "branch")
	var dead_ends = branches.filter(func(seg): return seg.get("is_dead_end", false))
	print("- Main paths: ", main_paths.size())
	print("- Branches: ", branches.size())
	print("- Dead ends: ", dead_ends.size())
	print("")
	print("Camp Statistics:")
	print("- Camp position: (", camp_position.x, ",", camp_position.y, ")")
	print("- Camp center: (", camp_center.x, ",", camp_center.y, ")")
	print("- Camp size: ", camp_size, "x", camp_size)
	print("")
	
	print_block_map_with_paths_safe()

func print_block_map_with_paths_safe():
	print("Block character map with paths (X = paths, W = water, C = camp):")
	print("")
	
	# Print in smaller chunks
	var chunk_size = 10
	for chunk_start in range(0, map_height, chunk_size):
		var chunk_end = min(chunk_start + chunk_size, map_height)
		
		for y in range(chunk_start, chunk_end):
			var row_string = ""
			for x in range(map_width):
				# Check priority: camp first, then paths, then water, then terrain
				if camp_data[y][x]:
					row_string += "C"
				elif path_data[y][x]:
					row_string += "X"
				elif water_data[y][x]:
					row_string += "W"
				else:
					var value = map_data[y][x]
					var block_char = get_block_character(value)
					row_string += block_char
			print(row_string)
		
		# Brief pause between chunks
		if chunk_end < map_height:
			await get_tree().process_frame

func get_block_character(value: float) -> String:
	# Use Unicode block characters for different densities
	if value >= 0.8:
		return "█" # Full block
	elif value >= 0.6:
		return "▓" # Dark shade
	elif value >= 0.4:
		return "▒" # Medium shade
	elif value >= 0.2:
		return "░" # Light shade
	else:
		return " " # Empty space

# Enhanced utility functions
func is_path_at(x: int, y: int) -> bool:
	if x >= 0 and x < map_width and y >= 0 and y < map_height:
		return path_data[y][x]
	return false

func is_water_at(x: int, y: int) -> bool:
	if x >= 0 and x < map_width and y >= 0 and y < map_height:
		return water_data[y][x]
	return false

func is_camp_at(x: int, y: int) -> bool:
	if x >= 0 and x < map_width and y >= 0 and y < map_height:
		return camp_data[y][x]
	return false

func get_terrain_at(x: int, y: int) -> float:
	if x >= 0 and x < map_width and y >= 0 and y < map_height:
		return map_data[y][x]
	return 0.0

func get_tile_type_at(x: int, y: int) -> String:
	if is_camp_at(x, y):
		return "camp"
	elif is_path_at(x, y):
		return "path"
	elif is_water_at(x, y):
		return "water"
	else:
		return "terrain"

func get_camp_position() -> Vector2:
	return camp_position

func get_camp_center() -> Vector2:
	return camp_center

func get_camp_size() -> int:
	return camp_size

func get_path_segments() -> Array:
	return path_segments

func get_main_paths() -> Array:
	return path_segments.filter(func(seg): return seg.type == "main")

func get_branches() -> Array:
	return path_segments.filter(func(seg): return seg.type == "branch")

func get_dead_ends() -> Array:
	var branches = get_branches()
	return branches.filter(func(seg): return seg.get("is_dead_end", false))

func export_map_as_image():
	print("Exporting map as image...")
	
	# Calculate image dimensions
	var image_width = map_width * tile_size
	var image_height = map_height * tile_size
	
	# Create image
	var image = Image.create(image_width, image_height, false, Image.FORMAT_RGB8)
	
	# Generate the image pixel by pixel
	for map_y in range(map_height):
		for map_x in range(map_width):
			var color = get_tile_color(map_x, map_y)
			
			# Fill the tile area with the color (each tile is tile_size x tile_size pixels)
			for pixel_y in range(tile_size):
				for pixel_x in range(tile_size):
					var image_x = map_x * tile_size + pixel_x
					var image_y = map_y * tile_size + pixel_y
					image.set_pixel(image_x, image_y, color)
	
	# Draw camp as a red dot at the center
	if camp_position.x >= 0 and camp_position.y >= 0:
		var camp_center_pixel_x = int(camp_center.x * tile_size)
		var camp_center_pixel_y = int(camp_center.y * tile_size)
		
		# Draw a red dot (3x3 pixels minimum, scaled with tile size)
		var dot_size = 20
		var half_dot = dot_size / 2
		
		for dy in range(-half_dot, half_dot + 1):
			for dx in range(-half_dot, half_dot + 1):
				var pixel_x = camp_center_pixel_x + dx
				var pixel_y = camp_center_pixel_y + dy
				
				# Make sure we're within image bounds
				if pixel_x >= 0 and pixel_x < image_width and pixel_y >= 0 and pixel_y < image_height:
					# Create a circular dot
					if dx * dx + dy * dy <= half_dot * half_dot:
						image.set_pixel(pixel_x, pixel_y, Color.RED)
	
	# Save the image - you can choose different locations:
	var file_path = "user://" + image_filename

	print("User data directory: ", OS.get_user_data_dir())
	print("Saving image to: ", file_path)
	
	var error = image.save_png(file_path)
	
	if error == OK:
		print("Map image saved successfully!")
		print("Image dimensions: ", image_width, "x", image_height, " pixels")
		print("Map dimensions: ", map_width, "x", map_height, " tiles")
		print("Tile size: ", tile_size, "x", tile_size, " pixels per tile")
		print("Camp represented as red dot at center: (", camp_center.x, ",", camp_center.y, ")")
	else:
		print("Error saving image: ", error)

func get_tile_color(x: int, y: int) -> Color:
	if is_camp_at(x, y):
		return Color(0.4, 0.7, 0.3)
	elif is_path_at(x, y):
		# Brown color for paths
		return Color(0.6, 0.4, 0.2) # Brown
	elif is_water_at(x, y):
		# Blue color for water
		return Color(0.2, 0.4, 0.8) # Blue
	else:
		# Terrain - dark green to light green based on height
		var terrain_value = get_terrain_at(x, y)
		
		# Create gradient from dark green to light green
		# Dark green for low values, light green for high values
		var dark_green = Color(0.1, 0.3, 0.1) # Dark green
		var light_green = Color(0.4, 0.7, 0.3) # Light green
		
		# Interpolate between dark and light green based on terrain value
		return dark_green.lerp(light_green, terrain_value)