"""
FreeExile Banana AI Asset Baker & Normal Map Synthesizer.
Processes all Banana AI generated images into authentic Dark Fantasy
2:1 isometric diamond floor tiles (128x64 px), wall blocks (128x128 px),
and interactive props with tangent-space normal maps (Blue > 128.0).
Complies with GEMINI.md hygiene (<= 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, ImageFilter

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\b4a236c5-d607-49d8-b15f-4217215ed2df")
STYLES_DIR = REPO_ROOT / "client" / "webapp" / "assets" / "map" / "styles"
MAP_DIR = REPO_ROOT / "client" / "webapp" / "assets" / "map"


def find_artifact(prefix: str) -> Path | None:
    """Find newest artifact matching 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."""
    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]:
    """Center crop and resize to 128x64 with 2:1 isometric diamond mask."""
    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."""
    with Image.open(src_path) as src:
        resized = src.resize((128, 128), Image.Resampling.LANCZOS).convert("RGBA")

    arr = np.array(resized, dtype=np.float32)
    # Subtle bottom ambient occlusion gradient
    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."""
    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
    return Image.fromarray(arr, mode="RGBA")


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

    out_img.save(out_path, optimize=True)
    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)


STYLE_TO_BIOME: Dict[str, str] = {
    "STY_01_HOANG_MANG_CO_LO": "biome_savage_trail",
    "STY_02_HUYET_SAT_LAM": "biome_blood_forest",
    "STY_03_BO_DA_TAN_XUONG": "biome_bone_strand",
    "STY_04_HOANG_GIOI_CUONG_THU": "biome_feral_steppes",
    "STY_05_KHAI_XUONG_CO_CAN": "biome_skeletal_basin",
    "STY_06_BAI_THA_MA_NGAP_MAN": "biome_mangrove_swamp",
    "STY_07_DAM_LAY_THOI_RUA": "biome_putrid_fen",
    "STY_08_VUC_DAM_BANG_DOC": "biome_bile_mire",
    "STY_09_HAC_THUY_MIEU_TRAP": "biome_spore_forest",
    "STY_10_DOC_CHIEU_MA_HO": "biome_spore_forest",
    "STY_11_HEM_NUI_XUONG_TRANG": "biome_bone_canyon",
    "STY_12_SA_COT_HOANG_MAC": "biome_bone_desert",
    "STY_13_CON_CAT_MA_QUY": "biome_bone_desert",
    "STY_14_VUC_DOI_NUOT_CHUNG": "biome_black_precipice",
    "STY_15_QUY_PHONG_HAC_NHAI": "biome_black_precipice",
    "STY_16_MO_QUANG_LUU_DAY": "biome_mine_shafts",
    "STY_17_DIA_DAO_HUYET_THACH": "biome_bloodstone_quarry",
    "STY_18_MIENG_LUA_NUNG_MAU": "biome_lava_caldera",
    "STY_19_HAM_LUA_LUYEN_NGUC": "biome_lava_caldera",
    "STY_20_VUC_THAM_XICH_THIET": "biome_lava_caldera",
    "STY_21_PHE_TICH_MA_THAN": "biome_fiend_ruins",
    "STY_22_PHE_TICH_XE_THIT": "biome_slaughter_ruins",
    "STY_23_TE_DAN_MAN_HOANG": "biome_pagan_altar",
    "STY_24_BE_THO_QUY_DOAT_MENH": "biome_pagan_altar",
    "STY_25_VAN_KIEP_MIEU_DIA": "biome_crypt_woes",
    "STY_26_COI_HU_KHONG_TUYET_DIA": "biome_void_domain",
    "STY_27_VET_NUT_VO_HAN_HUYET_GIOI": "biome_scourge_flesh",
    "STY_28_THAO_THIET_XAM_THUC": "biome_scourge_flesh",
    "STY_29_U_MINH_TA_CANH": "biome_nether_phantasm",
    "STY_30_TAN_TICH_THIEN_CUNG_HOANG_PHE": "biome_celestial_ruins",
}

STYLE_TO_WALL: 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_granite_cliff",
    "STY_06_BAI_THA_MA_NGAP_MAN": "wall_dungeon_iron",
    "STY_07_DAM_LAY_THOI_RUA": "wall_dungeon_iron",
    "STY_08_VUC_DAM_BANG_DOC": "wall_dungeon_iron",
    "STY_09_HAC_THUY_MIEU_TRAP": "wall_dungeon_iron",
    "STY_10_DOC_CHIEU_MA_HO": "wall_dungeon_iron",
    "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_dungeon_iron",
    "STY_17_DIA_DAO_HUYET_THACH": "wall_dungeon_iron",
    "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_dungeon_iron",
    "STY_22_PHE_TICH_XE_THIT": "wall_dungeon_iron",
    "STY_23_TE_DAN_MAN_HOANG": "wall_dungeon_iron",
    "STY_24_BE_THO_QUY_DOAT_MENH": "wall_dungeon_iron",
    "STY_25_VAN_KIEP_MIEU_DIA": "wall_dungeon_iron",
    "STY_26_COI_HU_KHONG_TUYET_DIA": "wall_dungeon_iron",
    "STY_27_VET_NUT_VO_HAN_HUYET_GIOI": "wall_flesh_corruption",
    "STY_28_THAO_THIET_XAM_THUC": "wall_flesh_corruption",
    "STY_29_U_MINH_TA_CANH": "wall_dungeon_iron",
    "STY_30_TAN_TICH_THIEN_CUNG_HOANG_PHE": "wall_dungeon_iron",
}


def build_props_atlas(prop_imgs: List[Image.Image]) -> Image.Image:
    """Combine 3 64x64 props into 192x64 spritesheet."""
    atlas = Image.new("RGBA", (192, 64), (0, 0, 0, 0))
    for idx, p in enumerate(prop_imgs[:3]):
        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 Asset Pipeline ===")

    # 1. Bake Props
    print("\n--- Baking World Props ---")
    prop_defs = [
        ("prop_blood_totem", "prop_blood_totem.png"),
        ("prop_ancient_monolith", "prop_ancient_monolith.png"),
        ("prop_rotting_urn", "prop_rotting_urn.png"),
        ("prop_boss_gate", "prop_boss_gate.png"),
        ("prop_primal_chest", "prop_primal_chest.png"),
        ("prop_astral_portal", "astral_portal_device.png"),
    ]
    baked_props: List[Image.Image] = []
    for prefix, filename in prop_defs:
        art = find_artifact(prefix)
        if art:
            out_file = MAP_DIR / filename
            bake_prop_asset(art, out_file)
            print(f"  [OK] Baked {filename} from {art.name}")
            with Image.open(out_file) as p_img:
                baked_props.append(p_img.copy())
        else:
            print(f"  [WARN] Artifact for {prefix} not found")

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

    # 2. Cache baked biomes and walls to avoid redundant processing
    biome_cache: Dict[str, Tuple[Image.Image, Image.Image]] = {}
    wall_cache: Dict[str, Tuple[Image.Image, Image.Image]] = {}

    print("\n--- Baking 30 Map Styles (Floor & Wall) ---")
    styles = list_available_styles()
    for sty_id in styles:
        folder_name = sty_id.lower()
        target_dir = STYLES_DIR / folder_name
        if not target_dir.exists():
            continue

        # Floor
        biome_key = STYLE_TO_BIOME.get(sty_id, "biome_bone_canyon")
        if biome_key not in biome_cache:
            art = find_artifact(biome_key)
            if art:
                biome_cache[biome_key] = bake_floor_tile(art)
                print(f"  [Bake] Cached floor {biome_key} from {art.name}")
            else:
                print(f"  [WARN] Biome artifact {biome_key} not found")

        if biome_key in biome_cache:
            floor_img, floor_norm = biome_cache[biome_key]
            floor_img.save(target_dir / "floor.png", optimize=True)
            floor_norm.save(target_dir / "floor_normal.png", optimize=True)

        # Wall
        wall_key = STYLE_TO_WALL.get(sty_id, "wall_granite_cliff")
        if wall_key not in wall_cache:
            art = find_artifact(wall_key)
            if art:
                wall_cache[wall_key] = bake_wall_tile(art)
                print(f"  [Bake] Cached wall {wall_key} from {art.name}")
            else:
                print(f"  [WARN] Wall artifact {wall_key} not found")

        if wall_key in wall_cache:
            wall_img, wall_norm = wall_cache[wall_key]
            wall_img.save(target_dir / "wall.png", optimize=True)
            wall_norm.save(target_dir / "wall_normal.png", optimize=True)

        # Update props atlas with real Banana props
        if prop_atlas:
            prop_atlas.save(target_dir / "props.png", optimize=True)

    print("\n[SUCCESS] All 30 map styles and world props successfully updated with Banana AI real assets!")


if __name__ == "__main__":
    main()
