extends Resource
class_name IsometricMapData

var GRID_SIZE_WIDTH = 50 # play area size x
var GRID_SIZE_LENGTH = 30 # play area size y
var GRID_SIZE_HEIGHT = 30 # play area size z
var OFFSET = 0
var MAX_ARRAY_SIZE = 0

# The actual map data array
var map_data = []

func _init(width = 50, length = 30, height = 30, offset = 0):
    GRID_SIZE_WIDTH = width
    GRID_SIZE_LENGTH = length
    GRID_SIZE_HEIGHT = height
    OFFSET = offset
    _set_max_array_size()
    initialize_map_data()

func _set_max_array_size():
    MAX_ARRAY_SIZE = 0
    
    if MAX_ARRAY_SIZE < GRID_SIZE_WIDTH:
        MAX_ARRAY_SIZE = GRID_SIZE_WIDTH
    if MAX_ARRAY_SIZE < GRID_SIZE_LENGTH:
        MAX_ARRAY_SIZE = GRID_SIZE_LENGTH
    if MAX_ARRAY_SIZE < GRID_SIZE_HEIGHT:
        MAX_ARRAY_SIZE = GRID_SIZE_HEIGHT


func is_array_tile_valid(array_pos: Vector3i):
    if 0 < array_pos.x and array_pos.x < MAX_ARRAY_SIZE and 0 < array_pos.y and array_pos.y < MAX_ARRAY_SIZE and 0 < array_pos.z and array_pos.z < MAX_ARRAY_SIZE:
        return true
    return false

func get_array_neighbors(array_pos: Vector3i, movement_range: int) -> Array:
    var neighbors = []
    var visited = {array_pos: true}  # Using a dictionary for faster lookups
    var directions = [
        Vector3i(0, 0, 1),   # up
        Vector3i(0, 0, -1),  # down
        Vector3i(0, 1, 0),   # left
        Vector3i(0, -1, 0),  # right
        Vector3i(1, 0, 0),   # forward
        Vector3i(-1, 0, 0),  # backward
    ]
    
    # Use a breadth-first search approach
    var current_level = [array_pos]
    
    for distance in range(1, movement_range + 1):
        var next_level = []
        
        for current_pos in current_level:
            for dir in directions:
                var neighbor_pos = current_pos + dir
                
                # Check if we've already processed this position or if it's invalid
                if neighbor_pos in visited or not is_array_tile_valid(neighbor_pos):
                    continue
                
                # Mark as visited and add to results
                visited[neighbor_pos] = true
                neighbors.append(neighbor_pos)
                next_level.append(neighbor_pos)
        
        # Move to the next level of neighbors
        current_level = next_level
        
        # If no more positions to check at this level, we can stop early
        if current_level.is_empty():
            break
    
    return neighbors


func initialize_map_data():
    # Initialize empty map structure
    map_data = []
    
    for x in MAX_ARRAY_SIZE:
        var y_array = []
        for y in MAX_ARRAY_SIZE:
            var z_array = []
            for z in MAX_ARRAY_SIZE:
                z_array.append(null)  
            y_array.append(z_array)
        map_data.append(y_array)

# Get a tile at specific coordinates, return null if out of bounds
func get_tile(x, y, z):
    if x < 0 or x >= MAX_ARRAY_SIZE or y < 0 or y >= MAX_ARRAY_SIZE or z < 0 or z >= MAX_ARRAY_SIZE:
        return null
    return map_data[x][y][z]

# Set a tile at specific coordinates
func set_tile(x, y, z, tile_data):
    if x < 0 or x >= MAX_ARRAY_SIZE or y < 0 or y >= MAX_ARRAY_SIZE or z < 0 or z >= MAX_ARRAY_SIZE:
        return
    map_data[x][y][z] = tile_data

# Generate the isometric coordinates for a tile
func generate_iso_coords(x, y, z):
    var coord_x = x + (-1 * z) + OFFSET
    var coord_y = y + (-1 * z) - OFFSET
    return Vector2i(coord_x, coord_y)

# Create a map with default configuration
func create_default_map():
    # First initialize all tiles with null values
    for z in GRID_SIZE_HEIGHT:
        for y in GRID_SIZE_LENGTH:
            for x in GRID_SIZE_WIDTH:
                var iso_coords = generate_iso_coords(x, y, z)
                
                var tile_data = IsometricMapSystem.create_tile_data(
                    iso_coords.x, 
                    iso_coords.y, 
                    z,
                    # Give all tiles valid default atlas positions to avoid null errors
                    IsometricMapSystem.BLUE_ISOMETRICTILE_ATLAS_POSITION,
                    IsometricMapSystem.WHITE_ISOMETRICTILE_ATLAS_POSITION
                )
                # By default, tiles are not visible
                tile_data["visibility"] = false
                map_data[x][y][z] = tile_data
    
    # Set ground floor visible with blue tiles
    for y in GRID_SIZE_LENGTH:
        for x in GRID_SIZE_WIDTH:
            var tile = map_data[x][y][0]
            tile["atlas_base_position"] = IsometricMapSystem.BLUE_ISOMETRICTILE_ATLAS_POSITION
            tile["atlas_highlight_position"] = IsometricMapSystem.WHITE_ISOMETRICTILE_ATLAS_POSITION
            tile["visibility"] = true
            
    # Add a vertical column of red tiles for demonstration
    # Make sure coords are within bounds
    if 5 < GRID_SIZE_WIDTH and 5 < GRID_SIZE_LENGTH:
        for i in range(1, min(10, GRID_SIZE_HEIGHT)):
            var tile = map_data[5][5][i]
            if tile != null:
                tile["atlas_base_position"] = IsometricMapSystem.RED_ISOMETRICTILE_ATLAS_POSITION
                tile["atlas_highlight_position"] = IsometricMapSystem.WHITE_ISOMETRICTILE_ATLAS_POSITION
                tile["visibility"] = true
                IsometricMapSystem.debug_print("Created red tile at 5,5," + str(i))
    #var tile = map_data[10][10][1]
    #if tile != null:
    #    tile["atlas_base_position"] = IsometricMapSystem.BLACK_ISOMETRICTILE_ATLAS_POSITION
    #    tile["atlas_highlight_position"] = IsometricMapSystem.GREEN_ISOMETRICTILE_ATLAS_POSITION
    #    tile["visibility"] = true
    #    IsometricMapSystem.debug_print("Created black tile at 10,10," + str(1))
            
# Rotate map around X axis
func rotate_around_x_axis(rotation_steps = 1):
    rotation_steps = rotation_steps % 4
    
    if rotation_steps == 0:
        return
    
    var temp_map = []
    # Initialize temp_map with same structure
    for x in MAX_ARRAY_SIZE:
        var y_array = []
        for y in MAX_ARRAY_SIZE:
            var z_array = []
            for z in MAX_ARRAY_SIZE:
                z_array.append(null)
            y_array.append(z_array)
        temp_map.append(y_array)
    
    # Create a new map with adjusted dimensions
    var new_height = int(GRID_SIZE_LENGTH) if rotation_steps % 2 == 1 else int(GRID_SIZE_HEIGHT)
    var new_length = int(GRID_SIZE_HEIGHT) if rotation_steps % 2 == 1 else int(GRID_SIZE_LENGTH)
    
    # Store the original dimensions
    var original_height = GRID_SIZE_HEIGHT
    var original_length = GRID_SIZE_LENGTH
    
    # Temporarily adjust grid dimensions for coordinate calculation
    GRID_SIZE_HEIGHT = new_height
    GRID_SIZE_LENGTH = new_length
    
    for x in GRID_SIZE_WIDTH:
        for y in original_length:
            for z in original_height:
                if map_data[x][y][z] == null:
                    continue
                
                var new_y = 0
                var new_z = 0
                
                match rotation_steps:
                    1:  # 90 degrees
                        new_y = z
                        new_z = original_length - 1 - y
                    2:  # 180 degrees
                        new_y = original_length - 1 - y
                        new_z = original_height - 1 - z
                    3:  # 270 degrees
                        new_y = original_height - 1 - z
                        new_z = y
                
                if new_y >= 0 and new_y < new_length and new_z >= 0 and new_z < new_height:
                    temp_map[x][new_y][new_z] = map_data[x][y][z].duplicate()
                    
                    var new_coord_x = x + (-1 * new_z) + OFFSET
                    var new_coord_y = new_y + (-1 * new_z) - OFFSET
                    
                    temp_map[x][new_y][new_z]["x"] = new_coord_x
                    temp_map[x][new_y][new_z]["y"] = new_coord_y
                    temp_map[x][new_y][new_z]["z"] = new_z
    
    # Update the grid dimensions
    GRID_SIZE_HEIGHT = new_height
    GRID_SIZE_LENGTH = new_length
    
    map_data = temp_map

# Rotate map around Y axis
func rotate_around_y_axis(rotation_steps = 1):
    rotation_steps = rotation_steps % 4
    
    if rotation_steps == 0:
        return
    
    var temp_map = []
    # Initialize temp_map with same structure
    for x in MAX_ARRAY_SIZE:
        var y_array = []
        for y in MAX_ARRAY_SIZE:
            var z_array = []
            for z in MAX_ARRAY_SIZE:
                z_array.append(null)
            y_array.append(z_array)
        temp_map.append(y_array)
    
    # Create a new map with adjusted dimensions
    var new_width = int(GRID_SIZE_HEIGHT) if rotation_steps % 2 == 1 else int(GRID_SIZE_WIDTH)
    var new_height = int(GRID_SIZE_WIDTH) if rotation_steps % 2 == 1 else int(GRID_SIZE_HEIGHT)
    
    # Store the original dimensions
    var original_width = GRID_SIZE_WIDTH
    var original_height = GRID_SIZE_HEIGHT
    
    # Temporarily adjust grid dimensions for coordinate calculation
    GRID_SIZE_WIDTH = new_width
    GRID_SIZE_HEIGHT = new_height
    
    for x in original_width:
        for y in GRID_SIZE_LENGTH:
            for z in original_height:
                if map_data[x][y][z] == null:
                    continue
                
                var new_x = 0
                var new_z = 0
                
                match rotation_steps:
                    1:  # 90 degrees clockwise around Y axis
                        new_x = original_height - 1 - z
                        new_z = x
                    2:  # 180 degrees
                        new_x = original_width - 1 - x
                        new_z = original_height - 1 - z
                    3:  # 270 degrees
                        new_x = z
                        new_z = original_width - 1 - x
                
                if new_x >= 0 and new_x < new_width and new_z >= 0 and new_z < new_height:
                    temp_map[new_x][y][new_z] = map_data[x][y][z].duplicate()
                    
                    var new_coord_x = new_x + (-1 * new_z) + OFFSET
                    var new_coord_y = y + (-1 * new_z) - OFFSET
                    
                    temp_map[new_x][y][new_z]["x"] = new_coord_x
                    temp_map[new_x][y][new_z]["y"] = new_coord_y
                    temp_map[new_x][y][new_z]["z"] = new_z
    
    # Update the grid dimensions
    GRID_SIZE_WIDTH = new_width
    GRID_SIZE_HEIGHT = new_height
    
    map_data = temp_map

# Rotate map around Z axis
func rotate_around_z_axis(rotation_steps = 1):
    rotation_steps = rotation_steps % 4
    
    if rotation_steps == 0:
        return
    
    var temp_map = []
    # Initialize temp_map with same structure
    for x in MAX_ARRAY_SIZE:
        var y_array = []
        for y in MAX_ARRAY_SIZE:
            var z_array = []
            for z in MAX_ARRAY_SIZE:
                z_array.append(null)
            y_array.append(z_array)
        temp_map.append(y_array)
    
    # Create a new map with adjusted dimensions based on rotation
    var new_width = int(GRID_SIZE_LENGTH) if rotation_steps % 2 == 1 else int(GRID_SIZE_WIDTH)
    var new_length = int(GRID_SIZE_WIDTH) if rotation_steps % 2 == 1 else int(GRID_SIZE_LENGTH)
    
    # Store the original dimensions
    var original_width = GRID_SIZE_WIDTH
    var original_length = GRID_SIZE_LENGTH
    
    # Temporarily adjust grid dimensions for coordinate calculation
    GRID_SIZE_WIDTH = new_width
    GRID_SIZE_LENGTH = new_length
    
    for x in original_width:
        for y in original_length:
            for z in GRID_SIZE_HEIGHT:
                if map_data[x][y][z] == null:
                    continue
                
                var new_x = 0
                var new_y = 0
                
                match rotation_steps:
                    1:  # 90 degrees
                        new_x = y
                        new_y = original_width - 1 - x
                    2:  # 180 degrees
                        new_x = original_width - 1 - x
                        new_y = original_length - 1 - y
                    3:  # 270 degrees
                        new_x = original_length - 1 - y
                        new_y = x
                
                if new_x >= 0 and new_x < new_width and new_y >= 0 and new_y < new_length:
                    temp_map[new_x][new_y][z] = map_data[x][y][z].duplicate()
                    
                    var new_coord_x = new_x + (-1 * z) + OFFSET
                    var new_coord_y = new_y + (-1 * z) - OFFSET
                    
                    temp_map[new_x][new_y][z]["x"] = new_coord_x
                    temp_map[new_x][new_y][z]["y"] = new_coord_y
                    temp_map[new_x][new_y][z]["z"] = z
    
    # Update the grid dimensions
    GRID_SIZE_WIDTH = new_width
    GRID_SIZE_LENGTH = new_length
    
    map_data = temp_map

# Find a tile containing a specific unit
func find_unit_tile(unit_type = null):
    for z in GRID_SIZE_HEIGHT:
        for x in GRID_SIZE_WIDTH:
            for y in GRID_SIZE_LENGTH:
                var tile = map_data[x][y][z]
                if tile and tile["unit"] != null:
                    if unit_type == null or tile["unit_type"] == unit_type:
                        return {"tile": tile, "x": x, "y": y, "z": z}
    return null

# Move a unit from one position to another
func move_unit(from_x, from_y, from_z, to_x, to_y, to_z):
    # Get the source and destination tiles safely
    var from_tile = get_tile(from_x, from_y, from_z)
    var to_tile = get_tile(to_x, to_y, to_z)
    
    # Debug information
    print("Moving unit from [", from_x, ",", from_y, ",", from_z, "] to [", to_x, ",", to_y, ",", to_z, "]")
    
    # Validate that both tiles exist and source has a unit
    if from_tile == null:
        print("Source tile doesn't exist")
        return false
        
    if to_tile == null:
        print("Destination tile doesn't exist")
        return false
        
    if from_tile["unit"] == null:
        print("No unit at source tile")
        return false
    
    # Valid move - transfer the unit
    to_tile["unit"] = from_tile["unit"]
    to_tile["unit_type"] = from_tile["unit_type"]
    to_tile["visibility"] = true
    
    # Clear the source tile
    from_tile["unit"] = null
    from_tile["unit_type"] = null
    # Keep source tile visible
    from_tile["visibility"] = true
    
    print("Unit moved successfully")
    return true
