from abc import ABC, abstractmethod
from typing import Optional

from source.models.base import Maze, Cell


class PathFindingStrategy(ABC):
    """Интерфейс стратегии поиска пути в лабиринте."""

    @abstractmethod
    def find_path(
        self, maze: Maze, start: Cell = None, exit: Cell = None
    ) -> list[Cell]:
        """Найти путь от start до exit.

        Args:
            maze: Объект лабиринта.
            start: Стартовая клетка.
            exit: Целевая клетка.

        Returns:
            Список клеток пути (от start до exit включительно).
            Пустой список, если путь не найден.
        """

    def _validate(
        self, maze: Maze, start: Optional[Cell], exit: Optional[Cell]
    ) -> tuple[Optional[Cell], Optional[Cell]]:
        """Подставляет start/exit из лабиринта если не переданы явно.

        Raises:
            ValueError: Если старт или выход не найдены.
        """
        if start is None:
            start = maze.start
        if exit is None:
            exit = maze.exit

        if start is None:
            raise ValueError("Стартовая клетка не найдена в лабиринте")
        if exit is None:
            raise ValueError("Выходная клетка не найдена в лабиринте")

        return start, exit

    def _reconstruct_path(
        self, came_from: dict[Cell, Optional[Cell]], end: Cell
    ) -> list[Cell]:
        """Восстанавливает путь от старта до end, идя по came_from в обратном порядке.

        Args:
            came_from: Словарь {клетка: родитель}, где родитель старта = None.
            end: Конечная клетка.

        Returns:
            Список клеток пути от старта до end включительно.
            Пустой список, если end отсутствует в came_from.
        """
        if end not in came_from:
            return []

        path: list[Cell] = []
        current: Optional[Cell] = end
        while current is not None:
            path.append(current)
            current = came_from[current]
        path.reverse()
        return path
