#!/usr/bin/env python
# coding: utf-8

# In[ ]:


import time
from typing import List, Optional
from dataclasses import dataclass, field
from modelsMaze import Maze
from modelsCell import Cell
from strategiesPathfinding_strategy import PathFindingStrategy
from visualizationObserver import Observer

@dataclass
class SearchStats:
    """Статистика поиска."""
    algorithm_name: str
    time_ms: float
    visited_cells: int
    path_length: int
    path_found: bool = True

class MazeSolver:
    """
    Оркестратор для решения лабиринта.
    Использует паттерн Strategy для алгоритмов поиска.
    Поддерживает Observer для уведомлений.
    """

    def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
        self.maze = maze
        self._strategy = strategy
        self._observers: List[Observer] = []
        self._last_path: List[Cell] = []
        self._last_stats: Optional[SearchStats] = None

    def set_strategy(self, strategy: PathFindingStrategy) -> None:
        """Динамическая смена стратегии."""
        self._strategy = strategy
        self._notify(f"Стратегия изменена на {strategy.name}")

    def attach(self, observer: Observer) -> None:
        """Подписать наблюдателя."""
        self._observers.append(observer)

    def detach(self, observer: Observer) -> None:
        """Отписать наблюдателя."""
        if observer in self._observers:
            self._observers.remove(observer)

    def _notify(self, event: str) -> None:
        """Уведомить всех наблюдателей."""
        for observer in self._observers:
            observer.update(event)

    def solve(self) -> List[Cell]:
        """
        Выполнить поиск пути с текущей стратегией.
        Возвращает путь (список клеток).
        """
        if self._strategy is None:
            raise ValueError("Стратегия не установлена")

        if not self.maze.start_cell or not self.maze.exit_cell:
            raise ValueError("Лабиринт не имеет старта или выхода")

        self._notify(f"Начинаем поиск пути с использованием {self._strategy.name}...")

        start_time = time.perf_counter()
        path = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)
        end_time = time.perf_counter()

        time_ms = (end_time - start_time) * 1000

        # Получаем количество посещённых клеток из стратегии
        visited_cells = getattr(self._strategy, 'last_visited_count', 0)

        self._last_path = path
        self._last_stats = SearchStats(
            algorithm_name=self._strategy.name,
            time_ms=time_ms,
            visited_cells=visited_cells,
            path_length=len(path),
            path_found=len(path) > 0
        )

        if path:
            self._notify(f"Путь найден! Длина: {len(path)}, время: {time_ms:.2f} мс, посещено: {visited_cells}")
        else:
            self._notify(f"Путь не найден! Время: {time_ms:.2f} мс, посещено: {visited_cells}")

        return path

    @property
    def last_path(self) -> List[Cell]:
        return self._last_path

    @property
    def last_stats(self) -> Optional[SearchStats]:
        return self._last_stats

