from __future__ import annotations

import time
from dataclasses import dataclass

from .models import Cell, Maze
from .observers import Event, Observer
from .strategies import PathFindingStrategy


@dataclass(frozen=True)
class SearchStats:
    strategy_name: str
    time_ms: float
    visited_cells: int
    path_length: int
    path: list[Cell]


class MazeSolver:
    def __init__(self, maze: Maze, strategy: PathFindingStrategy) -> None:
        self.maze = maze
        self.strategy = strategy
        self._observers: list[Observer] = []

    def set_strategy(self, strategy: PathFindingStrategy) -> None:
        self.strategy = strategy

    def setStrategy(self, strategy: PathFindingStrategy) -> None:
        self.set_strategy(strategy)

    def add_observer(self, observer: Observer) -> None:
        self._observers.append(observer)

    def remove_observer(self, observer: Observer) -> None:
        self._observers.remove(observer)

    def solve(self) -> SearchStats:
        self._notify(Event("search_started", {"strategy": self.strategy.name}))
        started_at = time.perf_counter()
        result = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
        elapsed_ms = (time.perf_counter() - started_at) * 1000

        stats = SearchStats(
            strategy_name=self.strategy.name,
            time_ms=elapsed_ms,
            visited_cells=result.visited_count,
            path_length=len(result.path),
            path=result.path,
        )
        event_name = "path_found" if result.path else "path_not_found"
        self._notify(Event(event_name, {"stats": stats}))
        return stats

    def _notify(self, event: Event) -> None:
        for observer in self._observers:
            observer.update(event)
