"""
FreeExile Compact Binary Map Serializer and Deserializer.
Encodes MapGridData into high-performance Uint8Array binary format (1 byte/tile)
with a 16-byte fixed header, POI metadata table, and Encounter Zone table.
"""

from __future__ import annotations
import struct
from typing import List, Tuple, Dict
from server.world.map_data_types import (
    TileType,
    TileCell,
    MapGridData,
    EncounterZone,
)

HEADER_MAGIC = b"FE"
HEADER_FORMAT = "<2sBBHHBBBBBBH"
HEADER_SIZE = 16

# Primary mapping of 30 biome codes to their canonical IDs
CODE_TO_BIOME: Dict[int, str] = {
    1: "BLEACHED_BONE_CANYON",
    2: "SAVAGE_MANGROVE_SWAMP",
    3: "CRIMSON_BLOOD_FOREST",
    4: "OUTCAST_MINE_SHAFTS",
    5: "CORRUPTED_FIEND_RUINS",
    6: "STY_01_HOANG_MANG_CO_LO",
    7: "STY_03_BO_DA_TAN_XUONG",
    8: "STY_04_HOANG_GIOI_CUONG_THU",
    9: "STY_05_KHAI_XUONG_CO_CAN",
    10: "STY_07_DAM_LAY_THOI_RUA",
    11: "STY_08_VUC_DAM_BANG_DOC",
    12: "STY_09_HAC_THUY_MIEU_TRAP",
    13: "STY_10_DOC_CHIEU_MA_HO",
    14: "STY_12_SA_COT_HOANG_MAC",
    15: "STY_13_CON_CAT_MA_QUY",
    16: "STY_14_VUC_DOI_NUOT_CHUNG",
    17: "STY_15_QUY_PHONG_HAC_NHAI",
    18: "STY_17_DIA_DAO_HUYET_THACH",
    19: "STY_18_MIENG_LUA_NUNG_MAU",
    20: "STY_19_HAM_LUA_LUYEN_NGUC",
    21: "STY_20_VUC_THAM_XICH_THIET",
    22: "STY_22_PHE_TICH_XE_THIT",
    23: "STY_23_TE_DAN_MAN_HOANG",
    24: "STY_24_BE_THO_QUY_DOAT_MENH",
    25: "STY_25_VAN_KIEP_MIEU_DIA",
    26: "STY_26_COI_HU_KHONG_TUYET_DIA",
    27: "STY_27_VET_NUT_VO_HAN_HUYET_GIOI",
    28: "STY_28_THAO_THIET_XAM_THUC",
    29: "STY_29_U_MINH_TA_CANH",
    30: "STY_30_TAN_TICH_THIEN_CUNG_HOANG_PHE",
}

# Bidirectional mapping supporting legacy aliases, STY_ IDs, and string digits
BIOME_TO_CODE: Dict[str, int] = {
    # Legacy names
    "BLEACHED_BONE_CANYON": 1,
    "SAVAGE_MANGROVE_SWAMP": 2,
    "CRIMSON_BLOOD_FOREST": 3,
    "OUTCAST_MINE_SHAFTS": 4,
    "CORRUPTED_FIEND_RUINS": 5,
    # All 30 styles
    "STY_01_HOANG_MANG_CO_LO": 6,
    "STY_02_HUYET_SAT_LAM": 3,
    "STY_03_BO_DA_TAN_XUONG": 7,
    "STY_04_HOANG_GIOI_CUONG_THU": 8,
    "STY_05_KHAI_XUONG_CO_CAN": 9,
    "STY_06_BAI_THA_MA_NGAP_MAN": 2,
    "STY_07_DAM_LAY_THOI_RUA": 10,
    "STY_08_VUC_DAM_BANG_DOC": 11,
    "STY_09_HAC_THUY_MIEU_TRAP": 12,
    "STY_10_DOC_CHIEU_MA_HO": 13,
    "STY_11_HEM_NUI_XUONG_TRANG": 1,
    "STY_12_SA_COT_HOANG_MAC": 14,
    "STY_13_CON_CAT_MA_QUY": 15,
    "STY_14_VUC_DOI_NUOT_CHUNG": 16,
    "STY_15_QUY_PHONG_HAC_NHAI": 17,
    "STY_16_MO_QUANG_LUU_DAY": 4,
    "STY_17_DIA_DAO_HUYET_THACH": 18,
    "STY_18_MIENG_LUA_NUNG_MAU": 19,
    "STY_19_HAM_LUA_LUYEN_NGUC": 20,
    "STY_20_VUC_THAM_XICH_THIET": 21,
    "STY_21_PHE_TICH_MA_THAN": 5,
    "STY_22_PHE_TICH_XE_THIT": 22,
    "STY_23_TE_DAN_MAN_HOANG": 23,
    "STY_24_BE_THO_QUY_DOAT_MENH": 24,
    "STY_25_VAN_KIEP_MIEU_DIA": 25,
    "STY_26_COI_HU_KHONG_TUYET_DIA": 26,
    "STY_27_VET_NUT_VO_HAN_HUYET_GIOI": 27,
    "STY_28_THAO_THIET_XAM_THUC": 28,
    "STY_29_U_MINH_TA_CANH": 29,
    "STY_30_TAN_TICH_THIEN_CUNG_HOANG_PHE": 30,
}
for _c in range(1, 31):
    BIOME_TO_CODE[str(_c)] = _c


def serialize_map_grid(map_data: MapGridData) -> bytes:
    """Serializes MapGridData into compact binary bytes."""
    biome_val = map_data.biome
    if isinstance(biome_val, int):
        biome_code = biome_val
    elif isinstance(biome_val, str) and biome_val.isdigit():
        biome_code = int(biome_val)
    else:
        biome_code = BIOME_TO_CODE.get(str(biome_val), 1)
    sx, sy = map_data.spawn_point
    bx, by = map_data.boss_gate if map_data.boss_gate else (255, 255)
    pois = map_data.poi_points[:255]
    encounters = map_data.encounter_zones[:255]

    header = struct.pack(
        HEADER_FORMAT,
        HEADER_MAGIC,
        1,  # version
        biome_code,
        map_data.width,
        map_data.height,
        min(255, max(0, sx)),
        min(255, max(0, sy)),
        min(255, max(0, bx)),
        min(255, max(0, by)),
        len(pois),
        len(encounters),
        0,  # reserved
    )

    poi_bytes = bytearray()
    for px, py, ptype in pois:
        t_val = TileType.POI.value
        if isinstance(ptype, int):
            t_val = ptype
        poi_bytes.extend(struct.pack("<BBB", min(255, px), min(255, py), t_val))

    enc_bytes = bytearray()
    for enc in encounters:
        enc_bytes.extend(
            struct.pack(
                "<BBBBB",
                min(255, enc.min_x),
                min(255, enc.min_y),
                min(255, enc.max_x),
                min(255, enc.max_y),
                min(255, enc.tier),
            )
        )

    tile_bytes = bytearray(map_data.width * map_data.height)
    for y in range(map_data.height):
        row_offset = y * map_data.width
        for x in range(map_data.width):
            tile_bytes[row_offset + x] = map_data.tiles[y][x].tile_type.value

    return bytes(header + poi_bytes + enc_bytes + tile_bytes)


def deserialize_map_grid(buffer: bytes) -> MapGridData:
    """Deserializes binary bytes into MapGridData."""
    if len(buffer) < HEADER_SIZE:
        raise ValueError(f"Buffer too short for map header: {len(buffer)} bytes")

    (
        magic,
        version,
        biome_code,
        width,
        height,
        sx,
        sy,
        bx,
        by,
        poi_count,
        enc_count,
        _,
    ) = struct.unpack_from(HEADER_FORMAT, buffer, 0)

    if magic != HEADER_MAGIC:
        raise ValueError(f"Invalid map magic header: {magic!r}, expected {HEADER_MAGIC!r}")

    expected_len = HEADER_SIZE + poi_count * 3 + enc_count * 5 + width * height
    if len(buffer) < expected_len:
        raise ValueError(f"Buffer truncated: expected {expected_len} bytes, got {len(buffer)}")

    offset = HEADER_SIZE
    poi_points: List[Tuple[int, int, str]] = []
    for _ in range(poi_count):
        px, py, pt = struct.unpack_from("<BBB", buffer, offset)
        poi_points.append((px, py, f"poi_{pt}"))
        offset += 3

    encounter_zones: List[EncounterZone] = []
    for _ in range(enc_count):
        x1, y1, x2, y2, tier = struct.unpack_from("<BBBBB", buffer, offset)
        encounter_zones.append(EncounterZone(min_x=x1, min_y=y1, max_x=x2, max_y=y2, tier=tier))
        offset += 5

    tiles: List[List[TileCell]] = []
    for y in range(height):
        row: List[TileCell] = []
        row_offset = offset + y * width
        for x in range(width):
            t_val = buffer[row_offset + x]
            try:
                tt = TileType(t_val)
            except ValueError:
                tt = TileType.WALL
            row.append(
                TileCell(
                    x=x,
                    y=y,
                    tile_type=tt,
                    walkable=tt.is_passable(),
                    blocks_vision=tt.blocks_vision(),
                    movement_cost=tt.movement_cost(),
                )
            )
        tiles.append(row)

    boss_gate = (bx, by) if (bx != 255 and by != 255) else None
    biome = CODE_TO_BIOME.get(biome_code, "BLEACHED_BONE_CANYON")

    return MapGridData(
        width=width,
        height=height,
        seed=0,
        biome=biome,
        tiles=tiles,
        spawn_point=(sx, sy),
        boss_point=boss_gate if boss_gate else (width - 2, height - 2),
        poi_points=poi_points,
        encounter_zones=encounter_zones,
        boss_gate=boss_gate,
    )
