from __future__ import annotations

import random
from dataclasses import dataclass
from typing import Dict, List, Tuple

from maze_world import MazeWorld

WINDOW_SIZE = (960, 640)
PLAYER_SIZE = 28
PLAYER_SPEED = 255.0
ATTACK_COOLDOWN = 0.25
ATTACK_RANGE = 54
PLAYER_BASE_DAMAGE = 20
PLAYER_MAX_HP = 140
MONSTER_CONTACT_DAMAGE = 12
PLAYER_HIT_COOLDOWN = 0.75
MARGIN = 80

# Retro RPG palette
ROOM_BG = (16, 15, 26)
FLOOR_A = (52, 74, 64)
FLOOR_B = (58, 82, 70)
FLOOR_DOT = (68, 96, 84)
PLAYER_COLOR = (246, 214, 92)
PLAYER_OUTLINE = (76, 50, 14)
WALL_COLOR = (113, 94, 131)
WALL_DARK = (78, 64, 96)
TEXT_COLOR = (240, 236, 220)
DOOR_COLOR = (166, 123, 79)
CHEST_COLOR = (187, 132, 62)
CHEST_BAND = (238, 201, 94)
MONSTER_COLOR = (180, 76, 76)
PANEL_BG = (27, 24, 36)
RARITY_COLORS = {
    "Commun": (200, 200, 200),
    "Rare": (110, 170, 255),
    "Epic": (194, 110, 255),
    "Legendaire": (255, 180, 73),
}


@dataclass
class Weapon:
    name: str
    rarity: str
    damage: int


@dataclass
class Monster:
    x: float
    y: float
    hp: int
    speed: float

    @property
    def rect(self):
        return (int(self.x), int(self.y), 24, 24)


@dataclass
class Chest:
    x: int
    y: int
    opened: bool = False

    @property
    def rect(self):
        return (self.x, self.y, 28, 24)


@dataclass
class RoomState:
    walls: List["WallBlock"]
    chests: List[Chest]
    monsters: List[Monster]
    room_type: str


@dataclass
class WallBlock:
    x: int
    y: int
    w: int
    h: int
    kind: str  # stone, brick, moss

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


@dataclass
class GameContext:
    world: MazeWorld
    room_states: Dict[Tuple[int, int], RoomState]
    inventory: List[Weapon]
    equipped: Weapon | None
    show_inventory: bool
    message: str
    message_timer: float
    attack_cooldown: float
    fade_alpha: float
    player_hp: int
    player_hit_cooldown: float
    facing: str
    deaths: int


def door_rects(width: int, height: int, room_exits: set[str]) -> Dict[str, Tuple[int, int, int, int]]:
    door_w = 120
    door_h = 28
    doors: Dict[str, Tuple[int, int, int, int]] = {}
    if "N" in room_exits:
        doors["N"] = (width // 2 - door_w // 2, 0, door_w, door_h)
    if "S" in room_exits:
        doors["S"] = (width // 2 - door_w // 2, height - door_h, door_w, door_h)
    if "W" in room_exits:
        doors["W"] = (0, height // 2 - door_w // 2, door_h, door_w)
    if "E" in room_exits:
        doors["E"] = (width - door_h, height // 2 - door_w // 2, door_h, door_w)
    return doors


def move_to_new_room(direction: str, size: Tuple[int, int]) -> Tuple[float, float]:
    width, height = size
    center_x = width / 2 - PLAYER_SIZE / 2
    center_y = height / 2 - PLAYER_SIZE / 2
    offset = 8
    if direction == "N":
        return center_x, height - MARGIN - PLAYER_SIZE - offset
    if direction == "S":
        return center_x, MARGIN + offset
    if direction == "W":
        return width - MARGIN - PLAYER_SIZE - offset, center_y
    return MARGIN + offset, center_y


def resolve_collisions(player_rect, colliders, axis: str):
    for wall in colliders:
        if player_rect.colliderect(wall):
            if axis == "x":
                if player_rect.centerx < wall.centerx:
                    player_rect.right = wall.left
                else:
                    player_rect.left = wall.right
            else:
                if player_rect.centery < wall.centery:
                    player_rect.bottom = wall.top
                else:
                    player_rect.top = wall.bottom
    return player_rect


def outer_wall_colliders(doors, width: int, height: int):
    # Build outer walls with holes where doors exist.
    colliders = []
    door_n = doors.get("N")
    door_s = doors.get("S")
    door_w = doors.get("W")
    door_e = doors.get("E")

    if door_n:
        x, _, w, _ = door_n
        colliders.append((0, 0, x, MARGIN))
        colliders.append((x + w, 0, width - (x + w), MARGIN))
    else:
        colliders.append((0, 0, width, MARGIN))

    if door_s:
        x, y, w, _ = door_s
        colliders.append((0, y, x, MARGIN))
        colliders.append((x + w, y, width - (x + w), MARGIN))
    else:
        colliders.append((0, height - MARGIN, width, MARGIN))

    if door_w:
        _, y, _, h = door_w
        colliders.append((0, 0, MARGIN, y))
        colliders.append((0, y + h, MARGIN, height - (y + h)))
    else:
        colliders.append((0, 0, MARGIN, height))

    if door_e:
        x, y, _, h = door_e
        colliders.append((x, 0, MARGIN, y))
        colliders.append((x, y + h, MARGIN, height - (y + h)))
    else:
        colliders.append((width - MARGIN, 0, MARGIN, height))

    return [(a, b, c, d) for a, b, c, d in colliders if c > 0 and d > 0]


def generate_room_content(room_pos: Tuple[int, int], exits: set[str], width: int, height: int) -> RoomState:
    # Deterministic random per room: same room => same content.
    seed = (room_pos[0] * 92821) ^ (room_pos[1] * 68917) ^ 7171
    rng = random.Random(seed)

    walls: List[WallBlock] = []
    chests: List[Chest] = []
    monsters: List[Monster] = []

    left = MARGIN
    right = width - MARGIN
    top = MARGIN
    bottom = height - MARGIN
    center_x = width // 2
    center_y = height // 2

    # Coherent room generation on a tile-like grid.
    cell = 64
    cols = 12
    rows = 7
    grid = [[0 for _ in range(cols)] for _ in range(rows)]  # 0 walkable, 1 blocked

    def carve_line(a: Tuple[int, int], b: Tuple[int, int]) -> None:
        x, y = a
        while x != b[0]:
            grid[y][x] = 0
            x += 1 if b[0] > x else -1
        while y != b[1]:
            grid[y][x] = 0
            y += 1 if b[1] > y else -1
        grid[y][x] = 0

    # Base fill with obstacles.
    for gy in range(rows):
        for gx in range(cols):
            if rng.random() < 0.33:
                grid[gy][gx] = 1

    center = (cols // 2, rows // 2)
    grid[center[1]][center[0]] = 0

    door_cells = []
    if "N" in exits:
        door_cells.append((cols // 2, 0))
    if "S" in exits:
        door_cells.append((cols // 2, rows - 1))
    if "W" in exits:
        door_cells.append((0, rows // 2))
    if "E" in exits:
        door_cells.append((cols - 1, rows // 2))

    # Guaranteed paths from all doors to center.
    for dc in door_cells:
        carve_line(dc, center)
        for nx in range(max(0, dc[0] - 1), min(cols, dc[0] + 2)):
            for ny in range(max(0, dc[1] - 1), min(rows, dc[1] + 2)):
                grid[ny][nx] = 0

    # Extra random corridors for less rigid rooms.
    for _ in range(rng.randint(1, 3)):
        a = (rng.randint(0, cols - 1), rng.randint(0, rows - 1))
        b = (rng.randint(0, cols - 1), rng.randint(0, rows - 1))
        carve_line(a, b)

    # Build wall rects from blocked cells.
    for gy in range(rows):
        for gx in range(cols):
            if grid[gy][gx] != 1:
                continue
            x = left + gx * cell + 8
            y = top + gy * cell + 8
            w = cell - 16
            h = cell - 16
            kind_roll = rng.random()
            if kind_roll < 0.45:
                kind = "stone"
            elif kind_roll < 0.8:
                kind = "brick"
            else:
                kind = "moss"
            walls.append(WallBlock(x=x, y=y, w=w, h=h, kind=kind))

    # Enemy density profile
    roll = rng.random()
    if roll < 0.18:
        room_type = "camp"
        monster_count = rng.randint(8, 12)
    elif roll < 0.55:
        room_type = "dense"
        monster_count = rng.randint(4, 7)
    else:
        room_type = "normal"
        monster_count = rng.randint(1, 3)

    # Fewer chests overall.
    chest_count = 1 if rng.random() < 0.22 else 0

    blocked = [wall.rect for wall in walls]

    def free_spot(w: int, h: int) -> Tuple[int, int]:
        for _ in range(80):
            x = rng.randint(left + 26, right - w - 26)
            y = rng.randint(top + 26, bottom - h - 26)
            rect = (x, y, w, h)
            # Never block center or door lanes.
            if center_x - 70 < x < center_x + 70 and center_y - 70 < y < center_y + 70:
                continue
            if any(
                not (rect[0] + rect[2] < b[0] or rect[0] > b[0] + b[2] or rect[1] + rect[3] < b[1] or rect[1] > b[1] + b[3])
                for b in blocked
            ):
                continue
            return x, y
        return center_x + rng.randint(-140, 140), center_y + rng.randint(-90, 90)

    for _ in range(chest_count):
        x, y = free_spot(28, 24)
        chests.append(Chest(x=x, y=y))
        blocked.append((x - 8, y - 8, 44, 40))

    for _ in range(monster_count):
        x, y = free_spot(24, 24)
        monsters.append(Monster(x=float(x), y=float(y), hp=rng.randint(28, 58), speed=rng.uniform(45, 80)))

    return RoomState(walls=walls, chests=chests, monsters=monsters, room_type=room_type)


def roll_weapon(rng: random.Random) -> Weapon:
    table = [
        ("Commun", 0.62, (6, 10), ["Dague usee", "Masse en bois", "Couteau rouille"]),
        ("Rare", 0.26, (11, 16), ["Sabre d'Azur", "Lance de l'ombre", "Hache trempee"]),
        ("Epic", 0.10, (17, 23), ["Rapiere runique", "Fendoir astral", "Lame spectrale"]),
        ("Legendaire", 0.02, (24, 32), ["Excalir", "Rage de Titan", "Croc du Neant"]),
    ]
    r = rng.random()
    acc = 0.0
    for rarity, chance, dmg_range, names in table:
        acc += chance
        if r <= acc:
            return Weapon(name=rng.choice(names), rarity=rarity, damage=rng.randint(*dmg_range))
    rarity, _, dmg_range, names = table[-1]
    return Weapon(name=rng.choice(names), rarity=rarity, damage=rng.randint(*dmg_range))


def weapon_line(weapon: Weapon) -> str:
    return f"{weapon.name} [{weapon.rarity}] dmg:{weapon.damage}"


def wall_colors(kind: str) -> Tuple[Tuple[int, int, int], Tuple[int, int, int], Tuple[int, int, int]]:
    if kind == "brick":
        return (126, 84, 82), (90, 58, 58), (154, 109, 105)
    if kind == "moss":
        return (92, 118, 86), (68, 86, 63), (133, 155, 116)
    return (113, 94, 131), (78, 64, 96), (148, 128, 170)


def biome_floor_palette(room_type: str) -> Tuple[Tuple[int, int, int], Tuple[int, int, int], Tuple[int, int, int]]:
    if room_type == "camp":
        return (78, 56, 56), (88, 64, 64), (110, 78, 78)
    if room_type == "dense":
        return (44, 72, 90), (50, 78, 96), (68, 98, 116)
    return FLOOR_A, FLOOR_B, FLOOR_DOT


def draw_retro_floor(screen, width: int, height: int, time_ms: int):
    import pygame

    tile = 32
    pulse = 1 if (time_ms // 250) % 2 == 0 else 0
    for y in range(MARGIN, height - MARGIN, tile):
        for x in range(MARGIN, width - MARGIN, tile):
            color = FLOOR_A if ((x // tile) + (y // tile)) % 2 == 0 else FLOOR_B
            pygame.draw.rect(screen, color, (x, y, tile, tile))
            pygame.draw.rect(screen, FLOOR_DOT, (x + 4, y + 4, 3 + pulse, 3 + pulse))


def draw_biome_floor(screen, width: int, height: int, time_ms: int, room_type: str):
    import pygame

    floor_a, floor_b, floor_dot = biome_floor_palette(room_type)
    tile = 32
    pulse = 1 if (time_ms // 250) % 2 == 0 else 0
    for y in range(MARGIN, height - MARGIN, tile):
        for x in range(MARGIN, width - MARGIN, tile):
            color = floor_a if ((x // tile) + (y // tile)) % 2 == 0 else floor_b
            pygame.draw.rect(screen, color, (x, y, tile, tile))
            pygame.draw.rect(screen, floor_dot, (x + 4, y + 4, 3 + pulse, 3 + pulse))


def draw_torches(screen, room_exits: set[str], width: int, height: int, time_ms: int):
    import pygame

    flick = 2 if (time_ms // 90) % 2 == 0 else 0
    spots = []
    if "N" in room_exits:
        spots.extend([(width // 2 - 90, MARGIN + 12), (width // 2 + 90, MARGIN + 12)])
    if "S" in room_exits:
        spots.extend([(width // 2 - 90, height - MARGIN - 12), (width // 2 + 90, height - MARGIN - 12)])
    if "W" in room_exits:
        spots.extend([(MARGIN + 12, height // 2 - 90), (MARGIN + 12, height // 2 + 90)])
    if "E" in room_exits:
        spots.extend([(width - MARGIN - 12, height // 2 - 90), (width - MARGIN - 12, height // 2 + 90)])

    for tx, ty in spots[:6]:
        pygame.draw.rect(screen, (116, 82, 52), (tx - 3, ty - 7, 6, 12))
        pygame.draw.circle(screen, (255, 172, 70), (tx, ty - 10), 6 + flick)
        pygame.draw.circle(screen, (255, 225, 150), (tx, ty - 10), 3 + flick // 2)


def directional_attack_rect(player_rect, facing: str):
    import pygame

    size = 44
    if facing == "N":
        return pygame.Rect(player_rect.centerx - size // 2, player_rect.top - ATTACK_RANGE, size, ATTACK_RANGE)
    if facing == "S":
        return pygame.Rect(player_rect.centerx - size // 2, player_rect.bottom, size, ATTACK_RANGE)
    if facing == "W":
        return pygame.Rect(player_rect.left - ATTACK_RANGE, player_rect.centery - size // 2, ATTACK_RANGE, size)
    return pygame.Rect(player_rect.right, player_rect.centery - size // 2, ATTACK_RANGE, size)


def draw_weapon_on_player(screen, player_rect, weapon: Weapon | None, facing: str):
    import pygame

    if weapon is None:
        return
    rare_col = RARITY_COLORS.get(weapon.rarity, (220, 220, 220))
    shaft = (160, 120, 72)
    if facing in ("N", "S"):
        x = player_rect.centerx - 3
        y = player_rect.y - 10 if facing == "N" else player_rect.bottom - 2
        pygame.draw.rect(screen, shaft, (x, y, 6, 14))
        pygame.draw.rect(screen, rare_col, (x - 3, y - 6 if facing == "N" else y + 14, 12, 6))
    else:
        y = player_rect.centery - 3
        x = player_rect.x - 10 if facing == "W" else player_rect.right - 2
        pygame.draw.rect(screen, shaft, (x, y, 14, 6))
        pygame.draw.rect(screen, rare_col, (x - 6 if facing == "W" else x + 14, y - 3, 6, 12))

    # Rarety aura for stylish skin
    if weapon.rarity in ("Epic", "Legendaire"):
        pygame.draw.rect(screen, rare_col, player_rect.inflate(6, 6), 1)


def run() -> None:
    try:
        import pygame
    except ImportError:
        print("Pygame n'est pas installe. Lance: pip install pygame")
        return

    pygame.init()
    screen = pygame.display.set_mode(WINDOW_SIZE)
    pygame.display.set_caption("RPG Labyrinthe - Prototype")
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("consolas", 22)
    small_font = pygame.font.SysFont("consolas", 18)
    tiny_font = pygame.font.SysFont("consolas", 16)

    global_rng = random.Random(99)
    ctx = GameContext(
        world=MazeWorld(seed=7),
        room_states={},
        inventory=[],
        equipped=None,
        show_inventory=False,
        message="Explore les salles. E pour coffre, SPACE pour attaquer, TAB inventaire.",
        message_timer=5.0,
        attack_cooldown=0.0,
        fade_alpha=0.0,
        player_hp=PLAYER_MAX_HP,
        player_hit_cooldown=0.0,
        facing="S",
        deaths=0,
    )

    width, height = WINDOW_SIZE
    player_x = width / 2 - PLAYER_SIZE / 2
    player_y = height / 2 - PLAYER_SIZE / 2
    running = True

    while running:
        dt = clock.tick(60) / 1000.0
        ctx.attack_cooldown = max(0.0, ctx.attack_cooldown - dt)
        ctx.message_timer = max(0.0, ctx.message_timer - dt)
        ctx.player_hit_cooldown = max(0.0, ctx.player_hit_cooldown - dt)
        ctx.fade_alpha = max(0.0, ctx.fade_alpha - 280.0 * dt)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_TAB:
                    ctx.show_inventory = not ctx.show_inventory
                elif pygame.K_1 <= event.key <= pygame.K_8:
                    idx = event.key - pygame.K_1
                    if idx < len(ctx.inventory):
                        ctx.equipped = ctx.inventory[idx]
                        ctx.message = f"Equipe: {weapon_line(ctx.equipped)}"
                        ctx.message_timer = 3.0

        room = ctx.world.current_room()
        doors = door_rects(width, height, room.exits)
        if room.pos not in ctx.room_states:
            ctx.room_states[room.pos] = generate_room_content(room.pos, room.exits, width, height)
        state = ctx.room_states[room.pos]

        colliders = []
        outer_walls = outer_wall_colliders(doors, width, height)
        for wall in outer_walls:
            colliders.append(pygame.Rect(wall))
        for wall in state.walls:
            colliders.append(pygame.Rect(wall.rect))

        keys = pygame.key.get_pressed()
        vel_x = 0.0
        vel_y = 0.0
        if not ctx.show_inventory:
            if keys[pygame.K_q]:
                vel_x -= PLAYER_SPEED
                ctx.facing = "W"
            if keys[pygame.K_d]:
                vel_x += PLAYER_SPEED
                ctx.facing = "E"
            if keys[pygame.K_z]:
                vel_y -= PLAYER_SPEED
                ctx.facing = "N"
            if keys[pygame.K_s]:
                vel_y += PLAYER_SPEED
                ctx.facing = "S"
        if keys[pygame.K_ESCAPE]:
            running = False

        player_rect = pygame.Rect(int(player_x), int(player_y), PLAYER_SIZE, PLAYER_SIZE)

        # Move and collide on X then Y for smoother feel.
        player_rect.x += int(vel_x * dt)
        player_rect = resolve_collisions(player_rect, colliders, "x")

        player_rect.y += int(vel_y * dt)
        player_rect = resolve_collisions(player_rect, colliders, "y")

        # Monster basic AI
        touching_monster = False
        for monster in state.monsters:
            dx = (player_rect.centerx - (monster.x + 12))
            dy = (player_rect.centery - (monster.y + 12))
            dist = max(1.0, (dx * dx + dy * dy) ** 0.5)
            if dist < 220:
                monster.x += (dx / dist) * monster.speed * dt
                monster.y += (dy / dist) * monster.speed * dt
            m_rect = pygame.Rect(monster.rect)
            m_rect = resolve_collisions(m_rect, colliders, "x")
            m_rect = resolve_collisions(m_rect, colliders, "y")
            if m_rect.colliderect(player_rect):
                touching_monster = True
            monster.x = float(m_rect.x)
            monster.y = float(m_rect.y)

        if touching_monster and ctx.player_hit_cooldown <= 0:
            ctx.player_hp -= MONSTER_CONTACT_DAMAGE
            ctx.player_hit_cooldown = PLAYER_HIT_COOLDOWN
            ctx.message = f"Aie! -{MONSTER_CONTACT_DAMAGE} PV"
            ctx.message_timer = 1.2

        if ctx.player_hp <= 0:
            ctx.deaths += 1
            ctx.player_hp = PLAYER_MAX_HP
            ctx.world.player_pos = (0, 0)
            player_x = width / 2 - PLAYER_SIZE / 2
            player_y = height / 2 - PLAYER_SIZE / 2
            ctx.fade_alpha = 220.0
            ctx.message = "Vous etes KO. Respawn a la salle de depart."
            ctx.message_timer = 2.5
            continue

        # Attack
        if keys[pygame.K_SPACE] and ctx.attack_cooldown <= 0:
            ctx.attack_cooldown = ATTACK_COOLDOWN
            damage = PLAYER_BASE_DAMAGE + (ctx.equipped.damage if ctx.equipped else 0)
            attack_zone = directional_attack_rect(player_rect, ctx.facing)
            hit_count = 0
            survivors = []
            for monster in state.monsters:
                if attack_zone.colliderect(pygame.Rect(monster.rect)):
                    monster.hp -= damage
                    hit_count += 1
                if monster.hp > 0:
                    survivors.append(monster)
            if hit_count > 0:
                ctx.message = f"Attaque! {hit_count} monstre(s) touche(s), dmg {damage}"
                ctx.message_timer = 1.5
            state.monsters = survivors

        # Interact with chest
        if keys[pygame.K_e]:
            for chest in state.chests:
                if chest.opened:
                    continue
                if pygame.Rect(chest.rect).inflate(26, 26).colliderect(player_rect):
                    chest.opened = True
                    loot = roll_weapon(global_rng)
                    ctx.inventory.append(loot)
                    if ctx.equipped is None:
                        ctx.equipped = loot
                    ctx.message = f"Coffre: + {weapon_line(loot)}"
                    ctx.message_timer = 3.5
                    break

        # Allow crossing border only if player stands on a valid door.
        transitioned = False
        if "N" in doors:
            north = pygame.Rect(doors["N"])
            if player_rect.top <= north.bottom and north.left <= player_rect.centerx <= north.right:
                ctx.world.move("N")
                player_x, player_y = move_to_new_room("N", WINDOW_SIZE)
                transitioned = True
        if not transitioned and "S" in doors:
            south = pygame.Rect(doors["S"])
            if player_rect.bottom >= south.top and south.left <= player_rect.centerx <= south.right:
                ctx.world.move("S")
                player_x, player_y = move_to_new_room("S", WINDOW_SIZE)
                transitioned = True
        if not transitioned and "W" in doors:
            west = pygame.Rect(doors["W"])
            if player_rect.left <= west.right and west.top <= player_rect.centery <= west.bottom:
                ctx.world.move("W")
                player_x, player_y = move_to_new_room("W", WINDOW_SIZE)
                transitioned = True
        if not transitioned and "E" in doors:
            east = pygame.Rect(doors["E"])
            if player_rect.right >= east.left and east.top <= player_rect.centery <= east.bottom:
                ctx.world.move("E")
                player_x, player_y = move_to_new_room("E", WINDOW_SIZE)
                transitioned = True

        if not transitioned:
            player_x = float(player_rect.x)
            player_y = float(player_rect.y)
        else:
            ctx.message = f"Transition salle {ctx.world.current_room().pos}"
            ctx.message_timer = 1.2
            ctx.fade_alpha = 180.0

        screen.fill(ROOM_BG)
        draw_biome_floor(screen, width, height, pygame.time.get_ticks(), state.room_type)

        # Outer walls (with door gaps)
        for wall in outer_walls:
            pygame.draw.rect(screen, WALL_DARK, wall)
            pygame.draw.rect(screen, WALL_COLOR, wall, 3)

        # Internal walls
        for wall in state.walls:
            base_col, dark_col, hi_col = wall_colors(wall.kind)
            pygame.draw.rect(screen, dark_col, wall.rect)
            pygame.draw.rect(screen, base_col, wall.rect, 2)
            pygame.draw.line(screen, hi_col, (wall.x, wall.y), (wall.x + wall.w, wall.y), 1)

        # Doors
        for rect in doors.values():
            pygame.draw.rect(screen, DOOR_COLOR, rect)
            pygame.draw.rect(screen, (84, 56, 34), rect, 2)
        draw_torches(screen, room.exits, width, height, pygame.time.get_ticks())

        # Room content
        for chest in state.chests:
            x, y, w, h = chest.rect
            base_col = CHEST_COLOR if not chest.opened else (95, 95, 95)
            pygame.draw.rect(screen, base_col, (x, y, w, h))
            pygame.draw.rect(screen, CHEST_BAND, (x + 11, y, 6, h))
            if chest.opened:
                pygame.draw.line(screen, (40, 40, 40), (x + 3, y + 3), (x + w - 4, y + h - 4), 2)

        for monster in state.monsters:
            x, y, w, h = monster.rect
            pygame.draw.rect(screen, MONSTER_COLOR, (x, y, w, h))
            pygame.draw.rect(screen, (92, 36, 36), (x, y, w, h), 2)
            pygame.draw.circle(screen, (35, 15, 15), (x + 7, y + 9), 2)
            pygame.draw.circle(screen, (35, 15, 15), (x + 17, y + 9), 2)
            hp_ratio = max(0.0, min(1.0, monster.hp / 55))
            pygame.draw.rect(screen, (30, 30, 30), (x, y - 8, 24, 4))
            pygame.draw.rect(screen, (115, 225, 100), (x, y - 8, int(24 * hp_ratio), 4))

        # Player + shadow
        shadow = pygame.Rect(int(player_x) + 4, int(player_y) + 16, PLAYER_SIZE - 8, PLAYER_SIZE - 8)
        pygame.draw.ellipse(screen, (15, 15, 18), shadow)
        player_draw_rect = pygame.Rect(int(player_x), int(player_y), PLAYER_SIZE, PLAYER_SIZE)
        pygame.draw.rect(screen, PLAYER_COLOR, player_draw_rect)
        pygame.draw.rect(screen, PLAYER_OUTLINE, player_draw_rect, 2)
        draw_weapon_on_player(screen, player_draw_rect, ctx.equipped, ctx.facing)

        if ctx.attack_cooldown > 0:
            attack_zone = directional_attack_rect(player_draw_rect, ctx.facing)
            pygame.draw.rect(screen, (255, 190, 110), attack_zone, 2)

        info = (
            f"Salle: {room.pos} | Type: {state.room_type} | "
            f"Sorties: {''.join(sorted(room.exits))} | Salles connues: {len(ctx.world.rooms)}"
        )
        txt = font.render(info, True, TEXT_COLOR)
        screen.blit(txt, (14, 10))
        eq = weapon_line(ctx.equipped) if ctx.equipped else "Aucune"
        help_txt = small_font.render("ZQSD deplacer | SPACE attaquer | E ouvrir | TAB inventaire | ESC quitter", True, TEXT_COLOR)
        screen.blit(help_txt, (14, 40))
        equip_txt = small_font.render(f"Arme equipee: {eq}", True, TEXT_COLOR)
        screen.blit(equip_txt, (14, 64))
        hp_ratio = max(0.0, min(1.0, ctx.player_hp / PLAYER_MAX_HP))
        pygame.draw.rect(screen, (35, 30, 30), (14, 92, 220, 14))
        pygame.draw.rect(screen, (196, 70, 70), (14, 92, int(220 * hp_ratio), 14))
        screen.blit(tiny_font.render(f"PV: {ctx.player_hp}/{PLAYER_MAX_HP}  Morts: {ctx.deaths}", True, TEXT_COLOR), (14, 108))

        if ctx.message_timer > 0:
            msg = small_font.render(ctx.message, True, (255, 242, 166))
            screen.blit(msg, (14, 88))

        if ctx.show_inventory:
            panel = pygame.Rect(width - 392, 18, 374, 300)
            pygame.draw.rect(screen, PANEL_BG, panel, border_radius=10)
            pygame.draw.rect(screen, (122, 101, 160), panel, 2, border_radius=10)
            title = font.render("Inventaire", True, TEXT_COLOR)
            screen.blit(title, (panel.x + 14, panel.y + 10))
            if not ctx.inventory:
                screen.blit(small_font.render("Vide", True, TEXT_COLOR), (panel.x + 14, panel.y + 58))
            else:
                for i, weapon in enumerate(ctx.inventory[:8]):
                    col = RARITY_COLORS.get(weapon.rarity, TEXT_COLOR)
                    prefix = "*" if ctx.equipped is weapon else " "
                    slot_y = panel.y + 50 + i * 28
                    pygame.draw.rect(screen, (40, 36, 54), (panel.x + 12, slot_y, panel.w - 24, 24), border_radius=6)
                    pygame.draw.rect(screen, col, (panel.x + 12, slot_y, 6, 24))
                    line = f"{prefix}{i+1}. {weapon.name} | {weapon.rarity} | Dmg {weapon.damage}"
                    screen.blit(tiny_font.render(line, True, col), (panel.x + 24, slot_y + 5))
            hint = tiny_font.render("Touches 1..8 = equiper | TAB fermer", True, (200, 210, 232))
            screen.blit(hint, (panel.x + 14, panel.bottom - 22))

        if ctx.fade_alpha > 0:
            fade = pygame.Surface((width, height))
            fade.fill((0, 0, 0))
            fade.set_alpha(int(ctx.fade_alpha))
            screen.blit(fade, (0, 0))

        pygame.display.flip()

    pygame.quit()


if __name__ == "__main__":
    run()
