"""
FreeExile Map Data Models and Types.
Strictly typed dataclasses and domain enums for procedural world generation,
tile attributes, anti-bot dynamic obstacles, and war-fog states.
"""

from __future__ import annotations
from enum import Enum
from dataclasses import dataclass, field
from typing import Dict, List, Tuple, Optional, Any


class TileType(Enum):
    VOID = 0
    FLOOR = 1
    WALL = 2
    DESTRUCTIBLE_BARRICADE = 3
    MUD_POOL = 4
    SPIKE_TRAP = 5
    CRUMBLED_DEBRIS = 6
    BONE_PILE = 7
    POISON_VENT = 8
    CHASM = 9
    BOSS_GATE = 10
    BOSS_ALTAR = 11
    RUNIC_FLOOR = 12
    PATH = 13
    DENSE_TERRAIN = 14
    POI = 15
    ENCOUNTER_LOW = 16
    ENCOUNTER_MEDIUM = 17
    ENCOUNTER_HIGH = 18
    WATER = 19

    def is_passable(self) -> bool:
        """Returns True if the tile can naturally be walked on by characters."""
        return self not in (
            TileType.VOID,
            TileType.WALL,
            TileType.DESTRUCTIBLE_BARRICADE,
            TileType.CHASM,
            TileType.BOSS_GATE,
            TileType.WATER,
        )

    def blocks_vision(self) -> bool:
        """Returns True if the tile blocks line of sight in fog of war."""
        return self in (TileType.VOID, TileType.WALL, TileType.BOSS_GATE)

    def movement_cost(self) -> float:
        """Returns movement cost factor: PATH=0.8, DENSE_TERRAIN=1.43, MUD_POOL=2.0, else 1.0."""
        if self == TileType.PATH:
            return 0.8
        if self == TileType.DENSE_TERRAIN:
            return 1.43
        if self == TileType.MUD_POOL:
            return 2.0
        return 1.0


class FogState(Enum):
    UNEXPLORED = 0        # Completely black, server sends 0 entity data
    EXPLORED_FOGGED = 1   # Seen in past, static terrain visible, dynamic entities masked
    VISIBLE = 2           # Currently in line-of-sight, real-time updates


class ObstacleType(Enum):
    BARRICADE_WOOD_BONE = "BARRICADE_WOOD_BONE"  # Destructible gate/wall
    DEBRIS_COLLAPSE = "DEBRIS_COLLAPSE"          # Sinuosity rubble, forces S-curve
    MUD_QUAGMIRE = "MUD_QUAGMIRE"                # 60% movement speed penalty
    CONCEALED_SPIKES = "CONCEALED_SPIKES"        # Hidden puncture trap
    BONE_CLUSTER = "BONE_CLUSTER"                # Dense skeleton cluster
    BOSS_SEAL_BARRIER = "BOSS_SEAL_BARRIER"      # Heavy seal barrier at Boss Arena threshold
    BONE_BRAZIER = "BONE_BRAZIER"                # Decorative and tactical obstacle inside arena


@dataclass(slots=True)
class TileCell:
    x: int
    y: int
    tile_type: TileType
    walkable: bool = True
    blocks_vision: bool = False
    movement_cost: float = 1.0
    max_hp: int = 0
    current_hp: int = 0
    hazard_damage: int = 0


@dataclass(slots=True)
class ObstacleInstance:
    obstacle_id: str
    x: int
    y: int
    obstacle_type: ObstacleType
    hp: int = 100
    max_hp: int = 100
    is_destroyed: bool = False
    blocks_movement: bool = True
    blocks_vision: bool = False
    slow_multiplier: float = 1.0
    damage_on_step: int = 0


@dataclass(slots=True, frozen=True)
class EncounterZone:
    min_x: int
    min_y: int
    max_x: int
    max_y: int
    tier: int = 1  # 1=LOW, 2=MEDIUM, 3=HIGH


@dataclass(slots=True, frozen=True)
class MapRoom:
    room_id: int
    x: int
    y: int
    w: int
    h: int
    room_type: str = "normal"  # spawn, boss, poi, ambush, dead_end
    is_boss_room: bool = False
    boss_gate_pos: Optional[Tuple[int, int]] = None
    boss_altar_pos: Optional[Tuple[int, int]] = None

    @property
    def center(self) -> Tuple[int, int]:
        return (self.x + self.w // 2, self.y + self.h // 2)

    def contains(self, x: int, y: int) -> bool:
        return self.x <= x < self.x + self.w and self.y <= y < self.y + self.h

    def intersects(self, other: MapRoom, padding: int = 1) -> bool:
        return (
            self.x - padding <= other.x + other.w
            and self.x + self.w + padding >= other.x
            and self.y - padding <= other.y + other.h
            and self.y + self.h + padding >= other.y
        )


@dataclass(slots=True)
class MapGridData:
    width: int
    height: int
    seed: int
    biome: str
    tiles: List[List[TileCell]]
    obstacles: Dict[Tuple[int, int], ObstacleInstance] = field(default_factory=dict)
    rooms: List[MapRoom] = field(default_factory=list)
    spawn_point: Tuple[int, int] = (0, 0)
    boss_point: Tuple[int, int] = (0, 0)
    poi_points: List[Tuple[int, int, str]] = field(default_factory=list)
    dead_end_points: List[Tuple[int, int]] = field(default_factory=list)
    boss_room: Optional[MapRoom] = None
    boss_gate: Optional[Tuple[int, int]] = None
    boss_gate_breached: bool = False
    encounter_zones: List[EncounterZone] = field(default_factory=list)
    spatial_chunk_size: int = 16

    def is_in_bounds(self, x: int, y: int) -> bool:
        return 0 <= x < self.width and 0 <= y < self.height

    def is_walkable(self, x: int, y: int) -> bool:
        if not self.is_in_bounds(x, y):
            return False
        # Check active obstacle
        obs = self.obstacles.get((x, y))
        if obs and not obs.is_destroyed and obs.blocks_movement:
            return False
        return self.tiles[y][x].walkable

    def blocks_vision(self, x: int, y: int) -> bool:
        if not self.is_in_bounds(x, y):
            return True
        obs = self.obstacles.get((x, y))
        if obs and not obs.is_destroyed and obs.blocks_vision:
            return True
        return self.tiles[y][x].blocks_vision

    def get_chunk_coords(self, x: int, y: int) -> Tuple[int, int]:
        """Spatial hash chunk coordinate for spatial partitioning."""
        return (x // self.spatial_chunk_size, y // self.spatial_chunk_size)

    def is_in_boss_room(self, x: int, y: int) -> bool:
        """Checks if coordinate falls inside the end-of-map boss arena."""
        if self.boss_room:
            return self.boss_room.contains(x, y)
        return False

    def get_boss_arena_cells(self) -> List[Tuple[int, int]]:
        """Returns all coordinates comprising the Boss Arena."""
        if not self.boss_room:
            return []
        br = self.boss_room
        return [
            (x, y)
            for y in range(br.y, br.y + br.h)
            for x in range(br.x, br.x + br.w)
            if self.is_in_bounds(x, y)
        ]

    def get_walkables_count(self) -> int:
        """Returns total walkable floor cells across the grid."""
        return sum(1 for row in self.tiles for cell in row if cell.walkable)

    def to_binary(self) -> bytes:
        """Serializes map grid to compact binary wire format (Uint8Array)."""
        from server.world.map_binary_serializer import serialize_map_grid
        return serialize_map_grid(self)


@dataclass(slots=True)
class PlayerVisibilityState:
    player_id: str
    width: int
    height: int
    view_radius: int = 12
    fog_matrix: List[List[int]] = field(default_factory=list)
    explored_tiles_count: int = 0
    visible_tiles_count: int = 0
    last_pos: Tuple[int, int] = (0, 0)
