"""
FreeExile Banana AI Map Tiles & Environmental Props Processor (Oct 2026).
Processes 25 newly rendered Banana AI textures into 2:1 isometric diamond
floor tiles (128x64), modular wall blocks (128x128), and interactive props
with PBR tangent-space normal maps and mobile WebP optimization.
Complies with GEMINI.md hygiene standards (<= 350 lines).
"""

from __future__ import annotations
import os
from pathlib import Path
import sys
from typing import Dict, List, Tuple

import numpy as np
from PIL import Image, ImageDraw

REPO_ROOT = Path(__file__).resolve().parent.parent.parent
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

from server.world.map_style_catalog import list_available_styles
from tools.asset_pipeline.m4_normal_utils import compute_tangent_normal_map

ARTIFACTS_DIR = Path(r"C:\Users\Admin\.gemini\antigravity\brain\59bafa97-39ad-4b55-8651-c37859a91ca7")
MAP_DIR = REPO_ROOT / "client" / "webapp" / "assets" / "map"
STYLES_DIR = MAP_DIR / "styles"
PROPS_DIR = MAP_DIR / "props"


def find_artifact(prefix: str) -> Path | None:
    """Find the newest artifact matching a given prefix."""
    matches = sorted(ARTIFACTS_DIR.glob(f"{prefix}*.jpg"))
    return matches[-1] if matches else None


def make_iso_mask(w: int, h: int) -> Image.Image:
    """Create 2:1 isometric diamond alpha mask with antialiased borders."""
    mask = Image.new("L", (w, h), 0)
    d = ImageDraw.Draw(mask)
    d.polygon([(w // 2, 0), (w - 1, h // 2), (w // 2, h - 1), (0, h // 2)], fill=255)
    return mask


def bake_floor_tile(src_path: Path) -> Tuple[Image.Image, Image.Image]:
    """Crop and resize to 128x64 with 2:1 isometric diamond mask and normal map."""
    with Image.open(src_path) as src:
        w, h = src.size
        cw, ch = int(w * 0.75), int(w * 0.375)
        left = (w - cw) // 2
        top = (h - ch) // 2
        cropped = src.crop((left, top, left + cw, top + ch))
        resized = cropped.resize((128, 64), Image.Resampling.LANCZOS).convert("RGBA")

    mask = make_iso_mask(128, 64)
    r, g, b, _ = resized.split()
    masked = Image.merge("RGBA", (r, g, b, mask))

    normal_img = compute_tangent_normal_map(masked, strength=2.2)
    nr, ng, nb, _ = normal_img.split()
    normal_masked = Image.merge("RGBA", (nr, ng, nb, mask))
    return masked, normal_masked


def bake_wall_tile(src_path: Path) -> Tuple[Image.Image, Image.Image]:
    """Resize wall texture to 128x128 with ambient shadow gradient and normal map."""
    with Image.open(src_path) as src:
        resized = src.resize((128, 128), Image.Resampling.LANCZOS).convert("RGBA")

    arr = np.array(resized, dtype=np.float32)
    y_grad = np.linspace(1.0, 0.72, 128)[:, None, None]
    arr[:, :, :3] = np.clip(arr[:, :, :3] * y_grad, 0, 255)
    arr[:, :, 3] = 255
    wall_img = Image.fromarray(arr.astype(np.uint8), mode="RGBA")

    normal_img = compute_tangent_normal_map(wall_img, strength=2.5)
    return wall_img, normal_img


def remove_black_background(img: Image.Image, threshold: int = 24) -> Image.Image:
    """Remove pure black background for transparent game props with border cleanup."""
    rgba = img.convert("RGBA")
    arr = np.array(rgba)
    rgb_max = np.max(arr[:, :, :3], axis=2)
    alpha = np.clip((rgb_max.astype(np.float32) - threshold) / (threshold * 1.5) * 255, 0, 255).astype(np.uint8)
    arr[:, :, 3] = alpha

    # Clean outer single-pixel borders to guarantee zero alpha fringing
    arr[0, :, 3] = 0
    arr[-1, :, 3] = 0
    arr[:, 0, 3] = 0
    arr[:, -1, 3] = 0
    return Image.fromarray(arr, mode="RGBA")


def bake_prop_asset(src_path: Path, out_path: Path, max_dim: int = 256) -> Image.Image:
    """Bake transparent prop, tangent normal map, and WebP copies."""
    with Image.open(src_path) as src:
        transparent = remove_black_background(src)
        bbox = transparent.split()[3].getbbox()
        cropped = transparent.crop(bbox) if bbox else transparent

        # Fit into a power-of-two square with safe padding (max_dim x max_dim)
        canvas = Image.new("RGBA", (max_dim, max_dim), (0, 0, 0, 0))
        cropped.thumbnail((max_dim - 4, max_dim - 4), Image.Resampling.LANCZOS)
        ox = (max_dim - cropped.width) // 2
        oy = (max_dim - cropped.height) // 2
        canvas.paste(cropped, (ox, oy), cropped)
        out_img = canvas

    out_img.save(out_path, optimize=True)
    out_img.save(out_path.with_suffix(".webp"), format="WEBP", quality=90)

    normal_img = compute_tangent_normal_map(out_img, strength=2.8)
    normal_path = out_path.with_name(f"{out_path.stem}_normal.png")
    normal_img.save(normal_path, optimize=True)
    normal_img.save(normal_path.with_suffix(".webp"), format="WEBP", quality=90)
    return out_img


STYLE_BIOME_MAP: Dict[str, str] = {
    "STY_01_HOANG_MANG_CO_LO": "tile_savage_mud_trail",
    "STY_02_HUYET_SAT_LAM": "tile_blood_swamp",
    "STY_03_BO_DA_TAN_XUONG": "tile_bone_strand",
    "STY_04_HOANG_GIOI_CUONG_THU": "tile_savage_mud_trail",
    "STY_05_KHAI_XUONG_CO_CAN": "tile_bone_strand",
    "STY_06_BAI_THA_MA_NGAP_MAN": "tile_blood_swamp",
    "STY_07_DAM_LAY_THOI_RUA": "tile_blood_swamp",
    "STY_08_VUC_DAM_BANG_DOC": "tile_blood_swamp",
    "STY_09_HAC_THUY_MIEU_TRAP": "tile_ancient_crypt",
    "STY_10_DOC_CHIEU_MA_HO": "tile_blood_swamp",
    "STY_11_HEM_NUI_XUONG_TRANG": "tile_bone_desert",
    "STY_12_SA_COT_HOANG_MAC": "tile_bone_desert",
    "STY_13_CON_CAT_MA_QUY": "tile_bone_desert",
    "STY_14_VUC_DOI_NUOT_CHUNG": "tile_ancient_crypt",
    "STY_15_QUY_PHONG_HAC_NHAI": "tile_ancient_crypt",
    "STY_16_MO_QUANG_LUU_DAY": "tile_ancient_crypt",
    "STY_17_DIA_DAO_HUYET_THACH": "tile_fiend_ruins_gold",
    "STY_18_MIENG_LUA_NUNG_MAU": "tile_volcanic_obsidian",
    "STY_19_HAM_LUA_LUYEN_NGUC": "tile_volcanic_obsidian",
    "STY_20_VUC_THAM_XICH_THIET": "tile_volcanic_obsidian",
    "STY_21_PHE_TICH_MA_THAN": "tile_fiend_ruins_gold",
    "STY_22_PHE_TICH_XE_THIT": "tile_corrupted_flesh",
    "STY_23_TE_DAN_MAN_HOANG": "tile_fiend_ruins_gold",
    "STY_24_BE_THO_QUY_DOAT_MENH": "tile_fiend_ruins_gold",
    "STY_25_VAN_KIEP_MIEU_DIA": "tile_ancient_crypt",
    "STY_26_COI_HU_KHONG_TUYET_DIA": "tile_void_chasm_stars",
    "STY_27_VET_NUT_VO_HAN_HUYET_GIOI": "tile_corrupted_flesh",
    "STY_28_THAO_THIET_XAM_THUC": "tile_corrupted_flesh",
    "STY_29_U_MINH_TA_CANH": "tile_void_chasm_stars",
    "STY_30_TAN_TICH_THIEN_CUNG_HOANG_PHE": "tile_fiend_ruins_gold",
}

STYLE_WALL_MAP: Dict[str, str] = {
    "STY_01_HOANG_MANG_CO_LO": "wall_granite_cliff",
    "STY_02_HUYET_SAT_LAM": "wall_granite_cliff",
    "STY_03_BO_DA_TAN_XUONG": "wall_granite_cliff",
    "STY_04_HOANG_GIOI_CUONG_THU": "wall_granite_cliff",
    "STY_05_KHAI_XUONG_CO_CAN": "wall_bone_palisade",
    "STY_06_BAI_THA_MA_NGAP_MAN": "wall_crypt_catacomb",
    "STY_07_DAM_LAY_THOI_RUA": "wall_crypt_catacomb",
    "STY_08_VUC_DAM_BANG_DOC": "wall_crypt_catacomb",
    "STY_09_HAC_THUY_MIEU_TRAP": "wall_crypt_catacomb",
    "STY_10_DOC_CHIEU_MA_HO": "wall_crypt_catacomb",
    "STY_11_HEM_NUI_XUONG_TRANG": "wall_bone_palisade",
    "STY_12_SA_COT_HOANG_MAC": "wall_bone_palisade",
    "STY_13_CON_CAT_MA_QUY": "wall_bone_palisade",
    "STY_14_VUC_DOI_NUOT_CHUNG": "wall_granite_cliff",
    "STY_15_QUY_PHONG_HAC_NHAI": "wall_granite_cliff",
    "STY_16_MO_QUANG_LUU_DAY": "wall_crypt_catacomb",
    "STY_17_DIA_DAO_HUYET_THACH": "wall_crypt_catacomb",
    "STY_18_MIENG_LUA_NUNG_MAU": "wall_volcanic_basalt",
    "STY_19_HAM_LUA_LUYEN_NGUC": "wall_volcanic_basalt",
    "STY_20_VUC_THAM_XICH_THIET": "wall_volcanic_basalt",
    "STY_21_PHE_TICH_MA_THAN": "wall_crypt_catacomb",
    "STY_22_PHE_TICH_XE_THIT": "wall_corrupted_flesh",
    "STY_23_TE_DAN_MAN_HOANG": "wall_crypt_catacomb",
    "STY_24_BE_THO_QUY_DOAT_MENH": "wall_crypt_catacomb",
    "STY_25_VAN_KIEP_MIEU_DIA": "wall_crypt_catacomb",
    "STY_26_COI_HU_KHONG_TUYET_DIA": "wall_crypt_catacomb",
    "STY_27_VET_NUT_VO_HAN_HUYET_GIOI": "wall_corrupted_flesh",
    "STY_28_THAO_THIET_XAM_THUC": "wall_corrupted_flesh",
    "STY_29_U_MINH_TA_CANH": "wall_crypt_catacomb",
    "STY_30_TAN_TICH_THIEN_CUNG_HOANG_PHE": "wall_crypt_catacomb",
}


def build_props_atlas(prop_imgs: List[Image.Image]) -> Image.Image:
    """Combine 4 props into 256x64 (Power-of-Two) spritesheet."""
    atlas = Image.new("RGBA", (256, 64), (0, 0, 0, 0))
    for idx, p in enumerate(prop_imgs[:4]):
        thumb = p.copy()
        thumb.thumbnail((60, 60), Image.Resampling.LANCZOS)
        ox = idx * 64 + (64 - thumb.width) // 2
        oy = (64 - thumb.height) // 2
        atlas.paste(thumb, (ox, oy), thumb)
    return atlas


def main() -> None:
    print("=== FreeExile Banana AI Map & Props Pipeline (Oct 2026) ===")
    MAP_DIR.mkdir(parents=True, exist_ok=True)
    PROPS_DIR.mkdir(parents=True, exist_ok=True)

    # 1. Process and Deploy 10 Environmental Props
    print("\n--- [Phase 1] Processing 10 World Props ---")
    props_definitions = [
        "prop_gallows_tree",
        "prop_demon_spine_archway",
        "prop_ancient_stone_waypoint",
        "prop_rotting_shipwreck_ballista",
        "prop_cursed_iron_maiden",
        "prop_ancient_sarcophagus",
        "prop_toxic_spore_mushroom",
        "prop_exile_campsite_fire",
        "prop_torture_wheel_crucifix",
        "prop_eldritch_flesh_cocoon",
    ]
    baked_props: List[Image.Image] = []
    for prop_name in props_definitions:
        art = find_artifact(prop_name)
        if art:
            out_file = PROPS_DIR / f"{prop_name}.png"
            p_img = bake_prop_asset(art, out_file, max_dim=256)
            baked_props.append(p_img)
            # Also copy to root MAP_DIR for general engine access
            bake_prop_asset(art, MAP_DIR / f"{prop_name}.png", max_dim=256)
            print(f"  [OK] Processed prop: {prop_name}")
        else:
            print(f"  [WARN] Artifact for {prop_name} not found")

    prop_atlas = build_props_atlas(baked_props) if len(baked_props) >= 4 else None

    # 2. Process 10 Terrain Floor Diamond Tiles
    print("\n--- [Phase 2] Baking 10 Terrain Diamond Tiles ---")
    terrain_keys = [
        ("tile_bone_strand", "tile_strand.png"),
        ("tile_blood_swamp", "tile_swamp.png"),
        ("tile_volcanic_obsidian", "tile_lava.png"),
        ("tile_ancient_crypt", "tile_stone.png"),
        ("tile_bone_desert", "tile_sand.png"),
        ("tile_abyssal_ice", "tile_snow.png"),
        ("tile_corrupted_flesh", "tile_flesh.png"),
        ("tile_savage_mud_trail", "tile_dirt.png"),
        ("tile_fiend_ruins_gold", "tile_ruins.png"),
        ("tile_void_chasm_stars", "tile_void.png"),
    ]
    floor_cache: Dict[str, Tuple[Image.Image, Image.Image]] = {}
    for art_key, dest_name in terrain_keys:
        art = find_artifact(art_key)
        if art:
            floor_img, normal_img = bake_floor_tile(art)
            floor_cache[art_key] = (floor_img, normal_img)
            # Save to global MAP_DIR
            out_p = MAP_DIR / dest_name
            floor_img.save(out_p, optimize=True)
            floor_img.save(out_p.with_suffix(".webp"), format="WEBP", quality=90)
            norm_p = MAP_DIR / f"{out_p.stem}_normal.png"
            normal_img.save(norm_p, optimize=True)
            normal_img.save(norm_p.with_suffix(".webp"), format="WEBP", quality=90)
            print(f"  [OK] Baked global floor: {dest_name}")
        else:
            print(f"  [WARN] Floor artifact {art_key} not found")

    # 3. Process 5 Modular Wall Block Textures
    print("\n--- [Phase 3] Baking 5 Modular Wall Blocks ---")
    wall_keys = [
        "wall_granite_cliff",
        "wall_crypt_catacomb",
        "wall_bone_palisade",
        "wall_volcanic_basalt",
        "wall_corrupted_flesh",
    ]
    wall_cache: Dict[str, Tuple[Image.Image, Image.Image]] = {}
    for w_key in wall_keys:
        art = find_artifact(w_key)
        if art:
            w_img, w_norm = bake_wall_tile(art)
            wall_cache[w_key] = (w_img, w_norm)
            out_w = MAP_DIR / f"{w_key}.png"
            w_img.save(out_w, optimize=True)
            w_img.save(out_w.with_suffix(".webp"), format="WEBP", quality=90)
            norm_w = MAP_DIR / f"{w_key}_normal.png"
            w_norm.save(norm_w, optimize=True)
            w_norm.save(norm_w.with_suffix(".webp"), format="WEBP", quality=90)
            print(f"  [OK] Baked global wall: {w_key}")
        else:
            print(f"  [WARN] Wall artifact {w_key} not found")

    # 4. Deploy High-Res Assets to All 30 Map Biome Style Directories
    print("\n--- [Phase 4] Updating All 30 Map Style Directories ---")
    styles = list_available_styles()
    for sty_id in styles:
        s_folder = STYLES_DIR / sty_id.lower()
        if not s_folder.exists():
            s_folder.mkdir(parents=True, exist_ok=True)

        # Floor tile
        b_key = STYLE_BIOME_MAP.get(sty_id, "tile_bone_strand")
        if b_key in floor_cache:
            f_img, f_norm = floor_cache[b_key]
            f_img.save(s_folder / "floor.png", optimize=True)
            f_img.save(s_folder / "floor.webp", format="WEBP", quality=90)
            f_norm.save(s_folder / "floor_normal.png", optimize=True)
            f_norm.save(s_folder / "floor_normal.webp", format="WEBP", quality=90)

        # Wall block
        w_key = STYLE_WALL_MAP.get(sty_id, "wall_granite_cliff")
        if w_key in wall_cache:
            w_img, w_norm = wall_cache[w_key]
            w_img.save(s_folder / "wall.png", optimize=True)
            w_img.save(s_folder / "wall.webp", format="WEBP", quality=90)
            w_norm.save(s_folder / "wall_normal.png", optimize=True)
            w_norm.save(s_folder / "wall_normal.webp", format="WEBP", quality=90)

        # Props atlas
        if prop_atlas:
            prop_atlas.save(s_folder / "props.png", optimize=True)
            prop_atlas.save(s_folder / "props.webp", format="WEBP", quality=90)

        # Path tile (savage trail or ancient crypt)
        p_key = "tile_savage_mud_trail" if "hoang" in sty_id.lower() or "bo_da" in sty_id.lower() else "tile_ancient_crypt"
        if p_key in floor_cache:
            p_img, p_norm = floor_cache[p_key]
            p_img.save(s_folder / "path.png", optimize=True)
            p_img.save(s_folder / "path.webp", format="WEBP", quality=90)
            p_norm.save(s_folder / "path_normal.png", optimize=True)
            p_norm.save(s_folder / "path_normal.webp", format="WEBP", quality=90)

        # Liquid tile (blood swamp, volcanic lava, or void chasm)
        if "lua" in sty_id.lower() or "nung" in sty_id.lower():
            l_key = "tile_volcanic_obsidian"
        elif "hu_khong" in sty_id.lower() or "u_minh" in sty_id.lower():
            l_key = "tile_void_chasm_stars"
        elif "bang" in sty_id.lower():
            l_key = "tile_abyssal_ice"
        else:
            l_key = "tile_blood_swamp"
        if l_key in floor_cache:
            l_img, l_norm = floor_cache[l_key]
            l_img.save(s_folder / "liquid.png", optimize=True)
            l_img.save(s_folder / "liquid.webp", format="WEBP", quality=90)
            l_norm.save(s_folder / "liquid_normal.png", optimize=True)
            l_norm.save(s_folder / "liquid_normal.webp", format="WEBP", quality=90)

    print("\n[SUCCESS] All 25 Banana AI map assets baked and deployed to 30 styles!")


if __name__ == "__main__":
    main()
