from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional

from source.models.base import Maze, Cell
from source.settings import cell_mapping


# ---------------------------------------------------------------------------- #
#                                    События                                   #
# ---------------------------------------------------------------------------- #


@dataclass
class Event:
    """Событие, передаваемое наблюдателям.

    Attributes:
        type: Тип события ('maze_loaded', 'path_found', 'move', 'no_path').
        payload: Дополнительные данные события.
    """

    type: str
    payload: dict = None


# ---------------------------------------------------------------------------- #
#                             Интерфейс наблюдателя                            #
# ---------------------------------------------------------------------------- #


class Observer(ABC):
    """Интерфейс наблюдателя за событиями лабиринта."""

    @abstractmethod
    def update(self, event: Event) -> None:
        """Обрабатывает входящее событие.

        Args:
            event: Объект события с типом и данными.
        """


# ---------------------------------------------------------------------------
# Консольный наблюдатель
# ---------------------------------------------------------------------------


class ConsoleView(Observer):
    """Отображает состояние лабиринта и события в консоли."""

    # Символ игрока на карте
    PLAYER_SYMBOL = "P"
    PATH_SYMBOL = "·"

    def update(self, event: Event) -> None:
        """Реагирует на события и выводит информацию в консоль.

        Args:
            event: Объект события.
        """
        match event.type:
            case "maze_loaded":
                print("Лабиринт загружен.")
                self.render(event.payload["maze"])
            case "path_found":
                print(f"Путь найден! Длина: {event.payload['length']} шагов.")
                self.render(
                    event.payload["maze"],
                    path=event.payload["path"],
                )
            case "no_path":
                print("Путь не найден.")
            case "move":
                print(f"Ход: {event.payload['direction']}")
                self.render(
                    event.payload["maze"],
                    player=event.payload["player_cell"],
                    path=event.payload.get("path"),
                )
            case _:
                print(f"[событие] {event.type}")

    def render(
        self,
        maze: Maze,
        player: Optional[Cell] = None,
        path: Optional[list[Cell]] = None,
    ) -> None:
        """Рисует лабиринт в консоли.

        Путь отмечается символом '·', позиция игрока — 'P'.

        Args:
            maze: Объект лабиринта.
            player: Текущая клетка игрока (опционально).
            path: Список клеток найденного пути (опционально).
        """
        path_set = set(path) if path else set()
        rows, cols = maze.shape

        print("+" + "─" * cols + "+")
        for y in range(rows):
            row_str = "|"
            for x in range(cols):
                cell = maze[y, x]
                if player and cell is player:
                    row_str += self.PLAYER_SYMBOL
                elif cell in path_set:
                    row_str += self.PATH_SYMBOL
                else:
                    row_str += str(cell)
            row_str += "|"
            print(row_str)
        print("+" + "─" * cols + "+")
