from __future__ import annotations

from .enums import CellType
from .models import Bomb, GameState, Player, Position

_TILE_AIR = "  "
_TILE_WALL = "🧱"
_TILE_BOX = "📦"
_TILE_BOMB = "💣"
_TILE_EXPLOSION = "💥"
_TILE_ME = "🤖"

_OPPONENT_ICONS = ["👾", "🏃", "🚶", "💃", "🕺", "🦊", "🐼", "🐸"]


def render_field(state: GameState | None) -> str:
    if state is None:
        return "<nil game state>\n"

    width = state.field.width
    height = state.field.height
    if width <= 0 or height <= 0:
        return "<empty field>\n"

    grid: list[list[str]] = []
    for y in range(height):
        row: list[str] = []
        for x in range(width):
            cell = state.field.cell_at(Position(x, y))
            if cell == CellType.WALL:
                row.append(_TILE_WALL)
            elif cell == CellType.BOX:
                row.append(_TILE_BOX)
            else:
                row.append(_TILE_AIR)
        grid.append(row)

    for e in state.explosions:
        if _in_bounds(e, width, height):
            grid[e.y][e.x] = _TILE_EXPLOSION

    for bomb in state.bombs:
        if _in_bounds(bomb.pos, width, height):
            grid[bomb.pos.y][bomb.pos.x] = _TILE_BOMB

    icons = _opponent_icon_map(state)
    for player in state.opponents:
        if _in_bounds(player.pos, width, height):
            grid[player.pos.y][player.pos.x] = icons[player.id]

    if state.me is not None and _in_bounds(state.me.pos, width, height):
        grid[state.me.pos.y][state.me.pos.x] = _TILE_ME

    out: list[str] = []
    out.append("╔" + ("══" * width) + "╗\n")
    for y in range(height):
        out.append("║" + "".join(grid[y]) + "║\n")
    out.append("╚" + ("══" * width) + "╝\n")

    out.extend(_players_section(state, icons))
    out.extend(_bombs_section(state.bombs))
    out.append("Legend: [space] AIR  🧱 WALL  📦 BOX  💣 BOMB  💥 EXPLOSION  🤖 ME\n")

    return "".join(out)


def print_field(state: GameState | None) -> None:
    print(render_field(state), end="")


def _players_section(state: GameState, icons: dict[str, str]) -> list[str]:
    players = _stable_players(state)
    if not players:
        return []

    lines = ["--- PLAYERS ---\n"]
    for player in players:
        icon = icons[player.id]
        if state.me is not None and player.id == state.me.id:
            icon = _TILE_ME

        lines.append(
            f"{icon} Player {_short_player_id(player.id)} | Health: {player.health}, "
            f"Score: {player.score} | Pos: ({player.pos.x},{player.pos.y})\n"
        )
    return lines


def _bombs_section(bombs: list[Bomb]) -> list[str]:
    if not bombs:
        return []

    sorted_bombs = sorted(bombs, key=lambda b: (b.pos.y, b.pos.x, b.fuse))
    lines = ["--- BOMBS ---\n"]
    for bomb in sorted_bombs:
        lines.append(f"💣 at ({bomb.pos.x},{bomb.pos.y}) | Fuse: {bomb.fuse}\n")
    return lines


def _stable_players(state: GameState) -> list[Player]:
    if state.players:
        return sorted(list(state.players), key=lambda p: p.id)

    players: list[Player] = []
    if state.me is not None:
        players.append(state.me)
    players.extend(state.opponents)
    return sorted(players, key=lambda p: p.id)


def _opponent_icon_map(state: GameState) -> dict[str, str]:
    icons: dict[str, str] = {}

    taken: set[str] = set()
    if state.me is not None:
        taken.add(state.me.id)

    opponent_ids: list[str] = []
    for player in state.opponents:
        if player.id in taken:
            continue
        taken.add(player.id)
        opponent_ids.append(player.id)

    opponent_ids.sort()
    for idx, player_id in enumerate(opponent_ids):
        icons[player_id] = _OPPONENT_ICONS[idx % len(_OPPONENT_ICONS)]

    for player in _stable_players(state):
        if player.id not in icons:
            icons[player.id] = _OPPONENT_ICONS[len(icons) % len(_OPPONENT_ICONS)]

    return icons


def _short_player_id(player_id: str) -> str:
    if player_id == "":
        return "<unknown>"
    n = 4
    if len(player_id) <= n:
        return player_id
    return "..." + player_id[-n:]


def _in_bounds(pos: Position, width: int, height: int) -> bool:
    return 0 <= pos.x < width and 0 <= pos.y < height
