from abc import ABC, abstractmethod
from typing import List, Optional, Set
from enum import Enum
from maze_model import Maze, Cell


class EventType(Enum):
    PATH_FOUND = "path_found"
    MOVE = "move"
    MAZE_LOADED = "maze_loaded"
    SOLVE_START = "solve_start"
    SOLVE_END = "solve_end"


class Observer(ABC):

    @abstractmethod
    def update(self, event_type: EventType, data: any) -> None:
        pass


class ConsoleView(Observer):

    def __init__(self):
        self.last_path: Optional[List[Cell]] = None

    def update(self, event_type: EventType, data: any) -> None:
        if event_type == EventType.MAZE_LOADED:
            print("Лабиринт загружен")
        elif event_type == EventType.SOLVE_START:
            print("Начинается поиск пути...")
        elif event_type == EventType.SOLVE_END:
            print(f"Поиск завершён. Статистика: {data}")
        elif event_type == EventType.PATH_FOUND:
            self.last_path = data

    def render(self, maze: Maze, player_pos: Optional[Cell] = None,
               path: Optional[List[Cell]] = None) -> None: #рисует лаб
        import os
        os.system('cls' if os.name == 'nt' else 'clear')

        path_set = set(path) if path else set()

        # Верх
        print("┌" + "─" * maze.width + "┐")

        for y in range(maze.height):
            line = "│"
            for x in range(maze.width):
                cell = maze.get_cell(x, y)
                if player_pos and player_pos.x == x and player_pos.y == y:
                    line += "P"
                elif cell == maze.start:
                    line += "S"
                elif cell == maze.exit:
                    line += "E"
                elif cell is not None and cell.is_wall:
                    line += "#"
                elif path and cell in path_set:
                    line += "."
                else:
                    line += " "
            line += "│"
            print(line)

        # Низ
        print("└" + "─" * maze.width + "┘")

        if path:
            print(f"\nПуть найден! Длина: {len(path)} шагов")
        elif path == []:
            print("\nПуть не найден:(")


class Player:

    def __init__(self, start_cell: Cell):
        self.current_cell = start_cell

    def move_to(self, cell: Cell) -> None:
        self.current_cell = cell

    def get_position(self) -> Cell:
        return self.current_cell


class Direction(Enum):
    UP = (0, -1)
    DOWN = (0, 1)
    LEFT = (-1, 0)
    RIGHT = (1, 0)


class Command(ABC):

    @abstractmethod
    def execute(self) -> None:
        pass

    @abstractmethod
    def undo(self) -> None:
        pass


class MoveCommand(Command):

    def __init__(self, player: Player, maze: Maze, direction: Direction):
        self.player = player
        self.maze = maze
        self.direction = direction
        self.previous_cell = player.current_cell

    def execute(self) -> None:
        dx, dy = self.direction.value
        new_x = self.player.current_cell.x + dx
        new_y = self.player.current_cell.y + dy

        new_cell = self.maze.get_cell(new_x, new_y)
        if new_cell and new_cell.is_passable():
            self.previous_cell = self.player.current_cell
            self.player.move_to(new_cell)
            return True
        return False

    def undo(self) -> None:
        self.player.move_to(self.previous_cell)


class GameController:

    def __init__(self, maze: Maze, view: ConsoleView):
        if maze.start is None:
            raise ValueError("Лабиринт не имеет стартовой клетки")

        self.maze = maze
        self.view = view
        self.player = Player(maze.start)
        self.command_history: List[Command] = []
        self.found_path: Optional[List[Cell]] = None

    def move(self, direction: Direction) -> bool:
        command = MoveCommand(self.player, self.maze, direction)
        if command.execute():
            self.command_history.append(command)
            self._render()
            return True
        return False

    def undo(self) -> None:
        if self.command_history:
            command = self.command_history.pop()
            command.undo()
            self._render()

    def set_path(self, path: List[Cell]) -> None:
        self.found_path = path
        self._render()

    def _render(self) -> None:
        self.view.render(self.maze, self.player.get_position(), self.found_path)