extends RefCounted # Using RefCounted instead of Node since this is a utility class

enum DUNGEON_ENTITY_TYPES {
	ENEMY,
	OBJECT,
	TRAP
}

# Constants
const DOOR_MODIFIERS = [
	{"name": "Locked Door", "type": "Locked"},
	{"name": "Bomb Wall", "type": "Bombable"}
]

const FLOOR_TILES = [
	7,8,9,10,11,12,
	25,26,27,28,29,30,31,
	44,45,46,47,48,49,50,
	63,64,65,66,67,68,69
]

const WALL_VARIATIONS = [
	0.45,
	0.15,
	0.15,
	0.15,
	0.1
]

const OBJECT_TYPES = [
	{"name": "Barrel", "ti": [70], "ti2": [89], "openable": false, "liftable": true, "throwable": false, "hp": - 1, "pushable": true, "size": {"x": 1, "y": 1}},
	{"name": "Pot", "ti": [13, 14, 15], "ti2": [51, 52, 53], "openable": false, "liftable": true, "throwable": true, "hp": 1, "pushable": true, "size": {"x": 1, "y": 1}},
	{"name": "Chest", "ti": [108], "ti2": [127], "openable": true, "liftable": false, "throwable": false, "hp": - 1, "pushable": false, "size": {"x": 1, "y": 1}},
	{"name": "Bench", "ti": [35, 36], "ti2": [16, 17], "openable": false, "liftable": false, "throwable": false, "hp": - 1, "pushable": false, "size": {"x": 2, "y": 1}}
]

const MONSTER_TYPES = [
	{
		"name": "Goblin",
	},
	{
		"name": "Slime",
	},
	# ... other monster types similar to JS version
]

const TRAP_TYPES = [
	{"name": "Spike Trap", "description": "Spikes shoot up from the floor when triggered."},
	{"name": "Arrow Trap", "description": "Arrows fire from the walls when a player steps on a pressure plate."},
	# ... other trap types
]

const ROOM_MODIFIERS = [
	{"name": "Player Start", "description": "The players start here.", "type": "START", "negative_modifiers": {"min": 0, "max": 0}},
	{"name": "Exit", "description": "Room contains an exit.", "type": "EXIT", "negative_modifiers": {"min": 0, "max": 0}},
	# ... other room modifiers
]

# Main generation function
func generate_dungeon(map_size: Vector2, _num_rooms: int, min_room_size: int, max_room_size: int) -> Dictionary:
	# Initialize grid
	var grid = []
	var randgrid = []
	for x in range(map_size.x):
		grid.append([])
		randgrid.append([])
		for y in range(map_size.y):
			grid[x].append(0)
			randgrid[x].append(0)
	
	var all_rooms = []
	var all_doors = []
	
	# 1. Create first room at a random position
	var first_w = rand_range_i(min_room_size, max_room_size)
	var first_h = rand_range_i(min_room_size, max_room_size)
	var first_room = {
		"x": rand_range_i(4, map_size.x - first_w - 4), # Random position with buffer
		"y": rand_range_i(4, map_size.y - first_h - 4),
		"w": first_w,
		"h": first_h,
		"modifiers": []
	}
	
	set_floor(first_room, grid, map_size)
	all_rooms.append(first_room)

	var nrOfDoorErrors = 0
	var nrOfRoomErrors = 0
	
	# 2. Try to place rooms until we can't fit any more
	var attempts = 1000 # Prevent infinite loops
	while attempts > 0 and all_rooms.size() > 0:
		# Pick a random existing room
		var source_room = all_rooms[randi() % all_rooms.size()]
		
		# Try to place a new room near it
		var new_room = try_place_room_near(source_room, grid, map_size, min_room_size, max_room_size)
		if new_room.w > 0: # Valid room created
			set_floor(new_room, grid, map_size)
			all_rooms.append(new_room)
		
		attempts -= 1
		if attempts <= 0:
			nrOfRoomErrors += 1
			break
	
	# 3. Connect rooms with corridors/doors
	if all_rooms.size() > 1:
		var connected_rooms = {}
		for room in all_rooms:
			connected_rooms[room] = []
		
		# First pass: try to connect each room to its closest neighbors
		for room in all_rooms:
			var closest_rooms = find_closest_rooms(room, all_rooms)
			#print("Connecting room at ", room.x, ",", room.y)
			
			var connection_attempts = 0
			var max_connection_attempts = 3 # Try to connect to multiple neighbors
			
			for target_room in closest_rooms:
				if connection_attempts >= max_connection_attempts:
					break
					
				if not rooms_are_connected(room, target_room, all_doors):
					var door = create_corridor_between_rooms(room, target_room, grid)
					if door.size() > 0:
						#print("Created direct connection between rooms")
						set_door(door, grid)
						all_doors.append(door)
						connected_rooms[room].append(target_room)
						connected_rooms[target_room].append(room)
						connection_attempts += 1
		
		# Second pass: ensure all rooms are connected
		var attempts2 = 100
		while attempts2 > 0:
			var reachable = find_reachable_rooms(all_rooms[0], all_rooms, all_doors)
			#print("Reachable rooms: ", reachable.size(), "/", all_rooms.size())
			
			if reachable.size() == all_rooms.size():
				#print("All rooms connected!")
				break
			
			# Find an unreachable room and try to connect it
			for room in all_rooms:
				if not reachable.has(room):
					var connected = false
					
					# Try to connect to each reachable room until success
					for target_room in reachable:
						var door = create_corridor_between_rooms(room, target_room, grid)
						if door.size() > 0:
							set_door(door, grid)
							all_doors.append(door)
							connected = true
							break
					
					if not connected:
						# Try creating intermediate room with multiple positions
						for offset_x in [-2, 0, 2]:
							for offset_y in [-2, 0, 2]:
								var mid_room = create_intermediate_room(room, reachable[0], offset_x, offset_y)
								if is_valid_room_position(mid_room, grid, map_size):
									set_floor(mid_room, grid, map_size)
									all_rooms.append(mid_room)
									
									var door1 = create_corridor_between_rooms(room, mid_room, grid)
									var door2 = create_corridor_between_rooms(mid_room, reachable[0], grid)
									
									if door1.size() > 0 and door2.size() > 0:
										set_door(door1, grid)
										set_door(door2, grid)
										all_doors.append(door1)
										all_doors.append(door2)
										connected = true
										break
							if connected:
								break
					
					if connected:
						break
			
			attempts2 -= 1
			if attempts2 <= 0:
				nrOfDoorErrors += 1
				break
						
	for x in range(map_size.x):
		for y in range(map_size.y):
			if grid[x][y] == 0: # wall
				var rand = randf()
				var sum:float = 0.0
				for i in WALL_VARIATIONS.size():
					sum += WALL_VARIATIONS[i];
					if rand <= sum:
						randgrid[x][y] = i
						break
			elif grid[x][y] == 1: # floor
				if randf() < 0.6:
					randgrid[x][y] = 0
				else:
					randgrid[x][y] = randi_range(1,FLOOR_TILES.size()-1)
			elif grid[x][y] == 2: # door
				randgrid[x][y] = 0 # we dont care about these... only have 1 variant

	var startRoomIndex = randi_range(0,all_rooms.size()-1)
	all_rooms[startRoomIndex].modifiers.push_back(ROOM_MODIFIERS[0])
	
	var farthestRoom = null
	var maxDistance = 0
	var exitRoomIndex = -1
	var roomIndex = 0
	for r in all_rooms:
		var distance = abs(r.x - all_rooms[startRoomIndex].x) + abs(r.y - all_rooms[startRoomIndex].y)
		if (distance > maxDistance):
			maxDistance = distance
			farthestRoom = r
			exitRoomIndex = roomIndex
		roomIndex+=1
		pass
	
	farthestRoom.modifiers.push_back(ROOM_MODIFIERS[1])
	
	var entities = []
	var TILE_SIZE = 16
	
	roomIndex = 0
	#populate rooms and decide modifiers for rooms
	for r in all_rooms:
		if roomIndex != startRoomIndex and roomIndex != exitRoomIndex:
			var validRoomLocations = []
			var min_x = (r.x + 1)
			var max_x = ((r.x + r.w) - 1)
			var min_y = (r.y + 1)
			var max_y = ((r.y + r.h) - 1)
			for rw in range(min_x,max_x):
				for rh in range(min_y, max_y):
					validRoomLocations.push_back(Vector2(rw*TILE_SIZE - 8, rh*TILE_SIZE - 8)) # we assume entities are 16x16 are centered
			# bigger rooms can have a larger content number!		
			var randNrOfEntities = randi_range(0, 4)
			
			for entI in randNrOfEntities:
				
				var enttype:DUNGEON_ENTITY_TYPES = randi_range(0, DUNGEON_ENTITY_TYPES.size()-1) as DUNGEON_ENTITY_TYPES
				var entStats = {}
				# hand code to only be enemies atm
				if enttype == DUNGEON_ENTITY_TYPES.TRAP:
					enttype = DUNGEON_ENTITY_TYPES.OBJECT
				#enttype = DUNGEON_ENTITY_TYPES.OBJECT ## only objects now...
				var subtype = "goblin"
				if enttype == DUNGEON_ENTITY_TYPES.ENEMY:
					var randType = randi_range(0, 1)
					var cStats = CharacterStats.new()
					if randType == 1:
						cStats.hp = 2
						subtype = "slime"
					else:
						cStats.hp = 3
						cStats.skin = "res://assets/gfx/Puny-Characters/Layer 0 - Skins/Orc1.png"
						cStats.skin = "res://assets/gfx/Puny-Characters/Layer 0 - Skins/Orc2.png"
						
						var hair = 0
						if randf() > 0.6:
							hair = randi_range(1,13)
						cStats.setHair(hair, randi_range(0,8))
						var facialhair = 0
						if randf() > 0.75: # very uncommon for facial hair on goblins
							facialhair = randi_range(1,3)
						cStats.setFacialHair(facialhair, randi_range(0, 4))
						
						#cStats.add_on = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/GoblinEars1.png"
						cStats.add_on = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/GoblinEars2.png"
						#cStats.add_on = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/OrcJaw1.png"
						#cStats.add_on = "res://assets/gfx/Puny-Characters/Layer 7 - Add-ons/Orc Add-ons/OrcJaw2.png"
						# randomize if the goblin will have a weapon like dagger or sword
						# randomize if the goblin will have bow and arrows also
						# randomize if the goblin will have an armour and helmet etc.
					
					entStats = cStats.save()
				elif enttype == DUNGEON_ENTITY_TYPES.OBJECT:
					subtype = "pot"
				else:
					subtype = "spike"
					
				var posI = randi_range(0, validRoomLocations.size()-1)
					
				var entity = {
					"type": enttype,
					"subtype": subtype,
					"stats": entStats,
					"position": {
						"x": validRoomLocations[posI].x,
						"y": validRoomLocations[posI].y
					}
				}
				entities.push_back(entity)
				validRoomLocations.remove_at(posI) # this is now occupied... don't allow anything else spawn on it.
			
			# fill up modifiers per room
			if ROOM_MODIFIERS.size() > 2:
				r.modifiers.push_back(ROOM_MODIFIERS[randi_range(2, ROOM_MODIFIERS.size()-2)])
			pass
		roomIndex += 1
		
	
	
	return {
		"rooms": all_rooms,
		"entities": entities,
		"doors": all_doors,
		"grid": grid,
		"randgrid": randgrid, # grid containing actual tile index-ish(ish)
		"mapSize": map_size,
		"nrOfDoorErrors": nrOfDoorErrors,
		"nrOfRoomErrors": nrOfRoomErrors
	}

# Helper functions
func create_random_room(map_size: Vector2, min_size: int, max_size: int) -> Dictionary:
	var x = randi() % (int(map_size.x) - max_size - 2) + 1
	var y = randi() % (int(map_size.y) - max_size - 2) + 1
	var w = rand_range_i(min_size, max_size)
	var h = rand_range_i(min_size, max_size)
	return {"x": x, "y": y, "w": w, "h": h, "modifiers": []}

func rand_range_i(min_val: int, max_val: int) -> int:
	return min_val + (randi() % (max_val - min_val + 1))

func set_floor(room: Dictionary, grid: Array, map_size: Vector2) -> void:
	for x in range(room.x, room.x + room.w):
		for y in range(room.y, room.y + room.h):
			if x >= 0 and x < map_size.x and y >= 0 and y < map_size.y:
				grid[x][y] = 1 # Set as floor tile

# ... Additional helper functions and implementation details would follow ...
# ... previous code ...

func try_place_room_near(source_room: Dictionary, grid: Array, map_size: Vector2,
						min_room_size: int, max_room_size: int) -> Dictionary:
	var attempts = 20
	while attempts > 0:
		var w = rand_range_i(min_room_size, max_room_size)
		var h = rand_range_i(min_room_size, max_room_size)
		
		# Try all four sides of the source room
		var sides = ["N", "S", "E", "W"]
		sides.shuffle()
		
		for side in sides:
			var x = source_room.x
			var y = source_room.y
			
			match side:
				"N":
					x = source_room.x + (randi() % max(1, source_room.w - w))
					y = source_room.y - h - 4 # 4 tiles away
				"S":
					x = source_room.x + (randi() % max(1, source_room.w - w))
					y = source_room.y + source_room.h + 4
				"W":
					x = source_room.x - w - 4
					y = source_room.y + (randi() % max(1, source_room.h - h))
				"E":
					x = source_room.x + source_room.w + 4
					y = source_room.y + (randi() % max(1, source_room.h - h))
			
			if is_valid_room_position({"x": x, "y": y, "w": w, "h": h}, grid, map_size):
				return {"x": x, "y": y, "w": w, "h": h, "modifiers": []}
		
		attempts -= 1
	
	return {"x": 0, "y": 0, "w": 0, "h": 0, "modifiers": []}

func find_closest_rooms(room: Dictionary, all_rooms: Array) -> Array:
	if all_rooms.size() <= 1:
		return []
		
	var distances = []
	for other in all_rooms:
		if other == room:
			continue
		var dist = abs(room.x - other.x) + abs(room.y - other.y)
		distances.append({"room": other, "distance": dist})
	
	# Sort by distance
	if distances.size() > 0:
		distances.sort_custom(func(a, b): return a.distance < b.distance)
		# Return the rooms only, in order of distance
		return distances.map(func(item): return item.room)
	
	return []

func rooms_are_connected(room1: Dictionary, room2: Dictionary, doors: Array) -> bool:
	for door in doors:
		if (door.room1 == room1 and door.room2 == room2) or \
		   (door.room1 == room2 and door.room2 == room1):
			return true
	return false

func create_corridor_between_rooms(room1: Dictionary, room2: Dictionary, _grid: Array) -> Dictionary:
	# Determine if rooms are more horizontal or vertical from each other
	var dx = abs(room2.x - room1.x)
	var dy = abs(room2.y - room1.y)
	
	# Check if rooms are too far apart (more than 8 tiles)
	if dx > 8 and dy > 8:
		return {}
	
	if dx > dy:
		# Horizontal corridor
		var leftRoom = room1 if room1.x < room2.x else room2
		var rightRoom = room2 if room1.x < room2.x else room1
		
		# Check if rooms are horizontally adjacent (gap should be reasonable)
		if rightRoom.x - (leftRoom.x + leftRoom.w) > 8:
			return {}
			
		# Door must start at the right edge of left room plus 1 tile gap
		var door_x = leftRoom.x + leftRoom.w
		
		# Door y must be within both rooms' height ranges, accounting for walls
		var min_y = max(leftRoom.y + 1, rightRoom.y + 1) # +1 to account for walls
		var max_y = min(leftRoom.y + leftRoom.h - 2, rightRoom.y + rightRoom.h - 2) # -2 to ensure both tiles fit
		
		# Make sure we have a valid range
		if max_y < min_y:
			return {}
			
		# Pick a valid y position within the range
		var door_y = min_y + (randi() % max(1, max_y - min_y + 1))
		
		# Calculate actual width needed (distance between rooms)
		var door_width = rightRoom.x - (leftRoom.x + leftRoom.w + 1)
		# Use the larger of minimum width (4) or actual distance
		door_width = max(4, door_width + 1)
		
		# Create door with calculated width
		var door = {
			"x": door_x,
			"y": door_y,
			"w": door_width, # Use calculated width
			"h": 2, # Fixed height for horizontal doors
			"dir": "E" if leftRoom == room1 else "W",
			"room1": room1,
			"room2": room2
		}
		
		return door
	else:
		# Vertical corridor
		var topRoom = room1 if room1.y < room2.y else room2
		var bottomRoom = room2 if room1.y < room2.y else room1
		
		# Check if rooms are vertically adjacent (gap should be reasonable)
		if bottomRoom.y - (topRoom.y + topRoom.h) > 8:
			return {}
			
		# Door must start at the bottom edge of top room plus 1 tile gap
		var door_y = topRoom.y + topRoom.h
		
		# Door x must be within both rooms' width ranges, accounting for walls
		var min_x = max(topRoom.x + 1, bottomRoom.x + 1) # +1 to account for walls
		var max_x = min(topRoom.x + topRoom.w - 2, bottomRoom.x + bottomRoom.w - 2) # -2 to ensure both tiles fit
		
		# Make sure we have a valid range
		if max_x < min_x:
			return {}
			
		# Pick a valid x position within the range
		var door_x = min_x + (randi() % max(1, max_x - min_x + 1))
		
		# Calculate actual height needed (distance between rooms)
		var door_height = bottomRoom.y - (topRoom.y + topRoom.h + 1)
		# Use the larger of minimum height (4) or actual distance
		door_height = max(4, door_height + 1)
		
		# Create door with calculated height
		var door = {
			"x": door_x,
			"y": door_y,
			"w": 2, # Fixed width for vertical doors
			"h": door_height, # Use calculated height
			"dir": "S" if topRoom == room1 else "N",
			"room1": room1,
			"room2": room2
		}
		
		return door
	

func add_room_modifiers(rooms: Array) -> void:
	# Add start room modifier to first room
	rooms[0].modifiers.append(ROOM_MODIFIERS[0]) # START modifier
	
	# Add exit to last room
	rooms[-1].modifiers.append(ROOM_MODIFIERS[1]) # EXIT modifier
	
	# Add random modifiers to other rooms
	for i in range(1, rooms.size() - 1):
		if randf() < 0.3: # 30% chance for a modifier
			var available_modifiers = ROOM_MODIFIERS.slice(2, ROOM_MODIFIERS.size())
			# Only add modifier if there are available ones
			if available_modifiers.size() > 0:
				rooms[i].modifiers.append(available_modifiers[randi() % available_modifiers.size()])

func generate_all_room_objects(rooms: Array, doors: Array) -> Array:
	var room_objects = []
	
	# Generate objects for each room
	for room in rooms:
		var objects = generate_room_objects(room)
		room_objects.append_array(objects)
	
	# Add door modifiers
	for door in doors:
		if randf() < 0.2: # 20% chance for door modifier
			var modifier = DOOR_MODIFIERS[randi() % DOOR_MODIFIERS.size()]
			room_objects.append({
				"type": "Door",
				"x": door.x,
				"y": door.y,
				"modifier": modifier
			})
	
	return room_objects

func generate_room_objects(room: Dictionary) -> Array:
	var objects = []
	var max_objects = int((room.w * room.h) / 16) # Roughly one object per 16 tiles
	
	for _i in range(max_objects):
		if randf() < 0.7: # 70% chance to place each potential object
			var obj_type = OBJECT_TYPES[randi() % OBJECT_TYPES.size()]
			var x = rand_range_i(room.x + 1, room.x + room.w - obj_type.size.x - 1)
			var y = rand_range_i(room.y + 1, room.y + room.h - obj_type.size.y - 1)
			
			# Check if position is free
			var can_place = true
			for obj in objects:
				if abs(obj.x - x) < 2 and abs(obj.y - y) < 2:
					can_place = false
					break
			
			if can_place:
				objects.append({
					"type": obj_type.name,
					"x": x,
					"y": y,
					"properties": obj_type
				})
	
	return objects

func set_door(door: Dictionary, grid: Array) -> void:
	match door.dir:
		"N", "S":
			if door.h > 4:
			# Set 2x4 corridor
			for dx in range(2):
				for dy in range(door.h):
					#if grid[door.x + dx][door.y + dy] != 1: # Don't overwrite room
					grid[door.x + dx][door.y + dy] = 2
		"E", "W":
			if door.w > 4:
			# Set 4x2 corridor
			for dx in range(door.w):
				for dy in range(2):
					#if grid[door.x + dx][door.y + dy] != 1: # Don't overwrite room
					grid[door.x + dx][door.y + dy] = 2

func is_valid_room_position(room: Dictionary, grid: Array, map_size: Vector2) -> bool:
	# Check if room is within map bounds with buffer
	if room.x < 4 or room.y < 4 or room.x + room.w >= map_size.x - 4 or room.y + room.h >= map_size.y - 4:
		#print("Room outside map bounds")
		return false
	
	# Check if room overlaps with existing rooms or corridors
	# Check the actual room area plus 4-tile buffer for spacing
	for x in range(room.x - 4, room.x + room.w + 4):
		for y in range(room.y - 4, room.y + room.h + 4):
			if x >= 0 and x < map_size.x and y >= 0 and y < map_size.y:
				if grid[x][y] != 0: # If tile is not empty
					#print("Room overlaps at ", Vector2(x, y))
					return false
	
	return true

# Add this helper function to check room connectivity
func find_reachable_rooms(start_room: Dictionary, _all_rooms: Array, all_doors: Array) -> Array:
	var reachable = [start_room]
	var queue = [start_room]
	
	while queue.size() > 0:
		var current = queue.pop_front()
		for door in all_doors:
			var next_room = null
			if door.room1 == current:
				next_room = door.room2
			elif door.room2 == current:
				next_room = door.room1
				
			if next_room != null and not reachable.has(next_room):
				reachable.append(next_room)
				queue.append(next_room)
	
	return reachable

func create_intermediate_room(room1: Dictionary, room2: Dictionary, offset_x: int, offset_y: int) -> Dictionary:
	return {
		"x": room1.x + (room2.x - room1.x) / 2 + offset_x,
		"y": room1.y + (room2.y - room1.y) / 2 + offset_y,
		"w": 6,
		"h": 6,
		"modifiers": []
	}
