"""
STARWASTE - Wang Tile Selection
Algorithm for selecting correct Wang tile based on neighboring tiles
"""

from typing import Dict, Tuple, Optional


class WangTileSelector:
    """
    Wang tile selector for seamless terrain transitions.
    
    PixelLab Wang tilesets have 16 tiles but use NON-STANDARD arrangement!
    The tile index does NOT match binary corner pattern.
    
    We must use a lookup table based on actual corner analysis.
    """
    
    # PixelLab-specific Wang tile patterns (analyzed from actual textures)
    # Each tile defined by its 4 corners: (NW, NE, SW, SE)
    # 0 = lower terrain (dark), 1 = upper terrain (bright)
    WANG_PATTERNS = {
        0:  (1, 1, 0, 1),
        1:  (1, 0, 1, 0),
        2:  (0, 1, 0, 0),
        3:  (1, 1, 0, 0),
        4:  (0, 1, 1, 0),
        5:  (1, 0, 0, 0),
        6:  (0, 0, 0, 0),  # All lower
        7:  (0, 0, 0, 1),
        8:  (1, 0, 1, 1),
        9:  (0, 0, 1, 1),
        10: (0, 0, 1, 0),
        11: (0, 1, 0, 1),
        12: (1, 1, 1, 1),  # All upper
        13: (1, 1, 1, 0),
        14: (1, 0, 0, 1),
        15: (0, 1, 1, 1),
    }
    
    # Reverse lookup: pattern -> tile index
    PATTERN_TO_TILE = {pattern: idx for idx, pattern in WANG_PATTERNS.items()}
    
    @staticmethod
    def get_wang_tile_index(
        center_tile: int,
        neighbors: Dict[str, Optional[int]]
    ) -> int:
        """
        Get Wang tile index (0-15) based on center tile and its neighbors.
        
        Args:
            center_tile: The tile type at center position
            neighbors: Dict with keys: 'N', 'S', 'E', 'W', 'NW', 'NE', 'SW', 'SE'
                      Values are tile types or None if out of bounds
        
        Returns:
            Wang tile index (0-15)
        """
        # Determine corner values based on neighbors
        # PixelLab logic (analyzed from actual tiles):
        # Corner is "upper" (1) if that direction has DIFFERENT tile type
        # Corner is "lower" (0) if that direction has SAME tile type
        
        # For each corner, check the 3 tiles that touch it
        # NW corner: N, W, NW
        nw = 1 if WangTileSelector._is_different_biome(center_tile, neighbors, ['N', 'W', 'NW']) else 0
        
        # NE corner: N, E, NE
        ne = 1 if WangTileSelector._is_different_biome(center_tile, neighbors, ['N', 'E', 'NE']) else 0
        
        # SW corner: S, W, SW
        sw = 1 if WangTileSelector._is_different_biome(center_tile, neighbors, ['S', 'W', 'SW']) else 0
        
        # SE corner: S, E, SE
        se = 1 if WangTileSelector._is_different_biome(center_tile, neighbors, ['S', 'E', 'SE']) else 0
        
        pattern = (nw, ne, sw, se)
        
        # IMPORTANT: If this tile is the "upper" terrain in its tileset,
        # we need to INVERT the pattern (flip all corners)
        # Example: SAND in wasteland_sand tileset
        # - When all neighbors are SAND → (0,0,0,0) → should use tile_12 (all upper/sand)
        # - So we invert: (0,0,0,0) → (1,1,1,1) → tile_12 ✓
        if WangTileSelector.should_use_upper_terrain(center_tile):
            pattern = tuple(1 - v for v in pattern)
        
        # Return tile index for this pattern
        tile_index = WangTileSelector.PATTERN_TO_TILE.get(pattern, 6)
        
        return tile_index
    
    @staticmethod
    def _is_different_biome(
        center_tile: int,
        neighbors: Dict[str, Optional[int]],
        check_dirs: list
    ) -> bool:
        """
        Check if any of the specified neighbor directions has different tile type.
        
        A corner should be "upper" if ANY of its touching tiles is different.
        """
        for direction in check_dirs:
            neighbor_tile = neighbors.get(direction)
            
            # None = edge of world, treat as different
            if neighbor_tile is None:
                return True
            
            # Different tile type = transition needed
            if neighbor_tile != center_tile:
                return True
        
        # All neighbors same = no transition
        return False
    
    @staticmethod
    def get_tileset_for_biome_pair(tile_type: int) -> Optional[str]:
        """
        Get the tileset folder name for a given tile type.
        
        For single-biome areas, we use transition tilesets where the tile
        is the "lower" terrain (tile_00 = pure biome).
        
        Args:
            tile_type: Tile type ID (from constants.py)
        
        Returns:
            Tileset folder name or None
        """
        # Mapping from tile types to their tileset folders
        # These are the "lower" terrain in each transition tileset
        TILE_TO_TILESET = {
            0: None,  # VOID
            1: "wasteland_sand",       # WASTELAND is lower in wasteland_sand
            2: "wasteland_radioactive", # RADIOACTIVE is upper in wasteland_radioactive
            3: "concrete_ruins",        # RUINS is upper in concrete_ruins
            4: "metal_rock",            # METAL is lower in metal_rock
            5: "wasteland_deadforest",  # DEAD_FOREST is upper in wasteland_deadforest
            6: "toxic_water_sand",      # TOXIC_WATER is lower in toxic_water_sand
            7: "wasteland_sand",        # SAND is upper in wasteland_sand
            8: "wasteland_rock",        # ROCK is upper in wasteland_rock
            9: "wasteland_concrete",    # CONCRETE is upper in wasteland_concrete
        }
        
        return TILE_TO_TILESET.get(tile_type)
    
    @staticmethod
    def should_use_upper_terrain(tile_type: int) -> bool:
        """
        Determine if tile should use "upper" terrain from tileset.
        
        Some tiles are the "upper" part of their transition tileset.
        """
        # Tiles that are "upper" in their tilesets
        UPPER_TILES = {
            2,  # RADIOACTIVE (upper in wasteland_radioactive)
            3,  # RUINS (upper in concrete_ruins)
            5,  # DEAD_FOREST (upper in wasteland_deadforest)
            7,  # SAND (upper in wasteland_sand)
            8,  # ROCK (upper in wasteland_rock)
            9,  # CONCRETE (upper in wasteland_concrete)
        }
        
        return tile_type in UPPER_TILES


def load_tileset_with_tiles(biome_id: str, tileset_path: str):
    """
    Load complete tileset (tileset.png + metadata.json) and extract individual tiles.
    
    Returns:
        Dict with:
            - 'tiles': Dict[tile_id, PIL.Image] - extracted tile images
            - 'metadata': metadata dict
            - 'tileset_path': path to tileset directory
            - 'lower_id': base tile ID for lower terrain
            - 'upper_id': base tile ID for upper terrain
    """
    import json
    import os
    from PIL import Image
    
    tileset_dir = os.path.join("assets", "biomes", biome_id, tileset_path)
    tileset_img_path = os.path.join(tileset_dir, "tileset.png")
    metadata_path = os.path.join(tileset_dir, "metadata.json")
    
    if not os.path.exists(tileset_img_path):
        console.print(f"⚠️ Tileset image not found: {tileset_img_path}")
        return None
    
    if not os.path.exists(metadata_path):
        console.print(f"⚠️ Metadata not found: {metadata_path}")
        return None
    
    # Load tileset image
    tileset_img = Image.open(tileset_img_path)
    
    # Load metadata
    with open(metadata_path) as f:
        metadata = json.load(f)
    
    # Extract individual tiles using bounding boxes
    tiles = {}
    for tile_data in metadata["tileset_data"]["tiles"]:
        tile_id = tile_data["id"]
        bbox = tile_data["bounding_box"]
        
        # Crop tile from tileset
        tile_img = tileset_img.crop((
            bbox["x"], 
            bbox["y"],
            bbox["x"] + bbox["width"],
            bbox["y"] + bbox["height"]
        ))
        
        tiles[tile_id] = tile_img
    
    return {
        "tiles": tiles,
        "metadata": metadata,
        "tileset_path": tileset_path,
        "lower_id": metadata["base_tile_ids"]["lower"],
        "upper_id": metadata["base_tile_ids"]["upper"]
    }


def get_wang_tile_from_tileset(
    corner_types: Tuple[int, int, int, int],
    tileset_data: dict,
    biome_id: str
):
    """
    Get Wang tile image directly from loaded tileset data.
    
    Args:
        corner_types: (NW, NE, SW, SE) terrain IDs
        tileset_data: Result from load_tileset_with_tiles()
        biome_id: Biome ID for logging
    
    Returns:
        PIL.Image of the tile, or None
    """
    if not tileset_data:
        return None
    
    nw, ne, sw, se = corner_types
    corner_set = {nw, ne, sw, se}
    
    tiles = tileset_data["tiles"]
    metadata = tileset_data["metadata"]
    
    # Convert terrain IDs to Wang pattern (matching create_test_map.py logic)
    if 0 in corner_set:
        # Water-wasteland tileset: 0=water (lower), 1=wasteland (upper)
        tile_nw = 0 if nw == 0 else 1
        tile_ne = 0 if ne == 0 else 1
        tile_sw = 0 if sw == 0 else 1
        tile_se = 0 if se == 0 else 1
    else:
        # Wasteland-desert tileset: 1=wasteland (lower), 2=desert (upper)
        tile_nw = 1 if nw == 2 else 0  # CRITICAL: Match create_test_map.py line 333!
        tile_ne = 1 if ne == 2 else 0
        tile_sw = 1 if sw == 2 else 0
        tile_se = 1 if se == 2 else 0
    
    # Search for matching tile
    def corner_to_int(c):
        if c is None:
            return None
        if isinstance(c, str):
            return 0 if c.lower() == "lower" else 1
        return c
    
    for tile_data in metadata["tileset_data"]["tiles"]:
        corners = tile_data.get("corners", {})
        meta_nw = corner_to_int(corners.get("NW"))
        meta_ne = corner_to_int(corners.get("NE"))
        meta_sw = corner_to_int(corners.get("SW"))
        meta_se = corner_to_int(corners.get("SE"))
        
        if None in (meta_nw, meta_ne, meta_sw, meta_se):
            continue
        
        if (meta_nw == tile_nw and meta_ne == tile_ne and
            meta_sw == tile_sw and meta_se == tile_se):
            # Found match!
            tile_id = tile_data.get("id", "")
            return tiles.get(tile_id)
    
    # No match - return lower base tile
    return tiles.get(tileset_data["lower_id"])
