from abc import ABC, abstractmethod
from typing import Optional

from source.models.base import Maze, Cell


# ---------------------------------------------------------------------------- #
#                                     Игрок                                    #
# ---------------------------------------------------------------------------- #


class Player:
    """Хранит текущее положение игрока в лабиринте.

    Attributes:
        cell: Текущая клетка игрока.
    """

    def __init__(self, cell: Cell) -> None:
        """Инициализирует игрока на заданной клетке.

        Args:
            cell: Начальная клетка игрока.
        """
        self.cell = cell

    def __repr__(self) -> str:
        return f"Player(x={self.cell.x}, y={self.cell.y})"


# ---------------------------------------------------------------------------- #
#                               Интерфейс команды                              #
# ---------------------------------------------------------------------------- #


class Command(ABC):
    """Интерфейс команды с поддержкой отмены."""

    @abstractmethod
    def execute(self) -> bool:
        """Выполняет команду.

        Returns:
            True если команда выполнена успешно, False иначе.
        """

    @abstractmethod
    def undo(self) -> None:
        """Отменяет команду, восстанавливая предыдущее состояние."""


# ---------------------------------------------------------------------------- #
#                              Команда перемещения                             #
# ---------------------------------------------------------------------------- #

DIRECTIONS = {
    "w": (0, -1),
    "s": (0, 1),
    "a": (-1, 0),
    "d": (1, 0),
}


class MoveCommand(Command):
    """Перемещает игрока в заданном направлении.

    Сохраняет предыдущую клетку для возможности отмены хода.
    """

    def __init__(self, player: Player, direction: str, maze: Maze) -> None:
        """Инициализирует команду перемещения.

        Args:
            player: Объект игрока.
            direction: Направление ('w', 'a', 's', 'd').
            maze: Объект лабиринта для проверки проходимости.

        Raises:
            ValueError: Если направление не распознано.
        """
        if direction not in DIRECTIONS:
            raise ValueError(
                f"Неизвестное направление '{direction}'. Используй: w/a/s/d"
            )

        self._player = player
        self._direction = direction
        self._maze = maze
        self._prev_cell: Optional[Cell] = None

    def execute(self) -> bool:
        """Перемещает игрока если целевая клетка проходима.

        Returns:
            True если перемещение выполнено, False если клетка непроходима.
        """
        dx, dy = DIRECTIONS[self._direction]
        target = self._maze.get_cell(
            self._player.cell.x + dx,
            self._player.cell.y + dy,
        )

        if target is None or not target.is_possible():
            return False

        self._prev_cell = self._player.cell
        self._player.cell = target
        return True

    def undo(self) -> None:
        """Возвращает игрока на предыдущую клетку."""
        if self._prev_cell is not None:
            self._player.cell = self._prev_cell


# ---------------------------------------------------------------------------- #
#                                История команд                                #
# ---------------------------------------------------------------------------- #


class CommandHistory:
    """Хранит историю выполненных команд и позволяет отменять их.

    Example:
        history = CommandHistory()
        cmd = MoveCommand(player, 'w', maze)
        if cmd.execute():
            history.push(cmd)

        history.undo()  # отменяет последний успешный ход
    """

    def __init__(self) -> None:
        self._history: list[Command] = []

    def push(self, command: Command) -> None:
        """Добавляет выполненную команду в историю.

        Args:
            command: Успешно выполненная команда.
        """
        self._history.append(command)

    def undo(self) -> bool:
        """Отменяет последнюю команду из истории.

        Returns:
            True если отмена выполнена, False если история пуста.
        """
        if not self._history:
            print("Нечего отменять.")
            return False
        self._history.pop().undo()
        return True

    def clear(self) -> None:
        """Очищает историю команд."""
        self._history.clear()
