import time
from typing import List, Tuple, Optional
from models import Maze, Cell
from strategies import PathFindingStrategy


class SearchStats:
    def __init__(self, time_ms: float, visited: int, path_length: int):
        self.time_ms = time_ms
        self.visited_count = visited
        self.path_length = path_length

    def __str__(self):
        return (f"Время: {self.time_ms:.4f} мс | "
                f"Посещено: {self.visited_count} | "
                f"Длина пути: {self.path_length}")


class MazeSolver:
    def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
        self.maze = maze
        self.strategy = strategy
        self._observers = []

    def setStrategy(self, strategy: PathFindingStrategy):
        self.strategy = strategy

    def attach(self, observer):
        self._observers.append(observer)

    def detach(self, observer):
        self._observers.remove(observer)

    def _notify(self, event: str, data=None):
        for observer in self._observers:
            observer.update(event, data)

    def solve(self) -> Tuple[List[Cell], SearchStats]:
        if not self.strategy:
            raise ValueError("Стратегия поиска не установлена.")
        if not self.maze.start or not self.maze.exit:
            raise ValueError("В лабиринте не определены старт или выход.")

        self._notify("search_started", {"strategy": type(self.strategy).__name__})

        start_time = time.perf_counter()
        path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)
        end_time = time.perf_counter()

        elapsed_ms = (end_time - start_time) * 1000
        visited = self.strategy.visited_count
        path_len = len(path)

        stats = SearchStats(elapsed_ms, visited, path_len)

        if path:
            self._notify("path_found", {"path": path, "stats": stats})
        else:
            self._notify("path_not_found", {"stats": stats})

        return path, stats