import time
from dataclasses import dataclass
from typing import List, Optional

from maze import Maze, Cell
from pathfinding import PathFindingStrategy


@dataclass
class SearchStats:
    """Статистика поиска пути."""
    time_ms: float          # время выполнения в миллисекундах
    visited_cells: int      # сколько клеток посетил алгоритм
    path_length: int        # длина найденного пути
    algorithm_name: str     # какой алгоритм использовался
    maze_name: str          # название лабиринта


class MazeSolver:
    """
    Оркестратор поиска пути.
    Использует паттерн Strategy для переключения алгоритмов.
    """
    
    def __init__(self, maze: Maze, strategy: PathFindingStrategy = None):
        self.maze = maze
        self.strategy = strategy
        self._observers = []  # для паттерна Observer (Этап 5)
    
    def set_strategy(self, strategy: PathFindingStrategy):
        """
        Динамическая смена алгоритма.
        Без паттерна Strategy пришлось бы переписывать этот метод
        под каждый новый алгоритм.
        """
        self.strategy = strategy
    
    def add_observer(self, observer):
        """Добавление наблюдателя (подготовка к Этапу 5)."""
        self._observers.append(observer)
    
    def _notify_observers(self, event: str):
        """Уведомляет всех наблюдателей о событии."""
        for observer in self._observers:
            observer.update(event)
    
    def solve(self, maze_name: str = "unnamed") -> SearchStats:
        """
        Выполняет поиск пути и возвращает статистику.
        
        Args:
            maze_name: название лабиринта для отчёта
        
        Returns:
            SearchStats с результатами поиска
        """
        if not self.strategy:
            raise ValueError("Стратегия не установлена! Вызовите set_strategy()")
        
        # Уведомляем наблюдателей
        self._notify_observers("search_started")
        
        # Замер времени
        start_time = time.perf_counter()
        
        # Запускаем алгоритм (Strategy делает всю работу)
        path, visited_count = self.strategy.find_path(
            self.maze, self.maze.start, self.maze.exit
        )
        
        # Останавливаем замер
        end_time = time.perf_counter()
        time_ms = (end_time - start_time) * 1000
        
        # Уведомляем о результате
        event = "path_found" if path else "no_path"
        self._notify_observers(event)
        
        # Формируем статистику
        return SearchStats(
            time_ms=time_ms,
            visited_cells=visited_count,
            path_length=len(path),
            algorithm_name=self.strategy.get_name(),
            maze_name=maze_name
        )