"""
maze_solver/builder.py - паттерн Builder для создания лабиринтов.

Зачем Builder: процесс построения лабиринта сложный (чтение файла, парсинг,
валидация символов, простановка флагов, поиск старта и выхода). Builder
изолирует эти подробности от клиента; для нового формата (JSON, бинарный)
достаточно реализовать ещё один builder с тем же интерфейсом.
"""

from abc import ABC, abstractmethod
from .model import Cell, Maze


class MazeBuilder(ABC):
    """Абстрактный билдер лабиринта."""

    @abstractmethod
    def build_from_file(self, filename) -> Maze:
        """Возвращает готовый Maze."""


class TextFileMazeBuilder(MazeBuilder):
    """Билдер из текстового формата.

    Символы:
        '#' - стена
        ' ' - проход (вес 1)
        'S' - старт (проходим)
        'E' - выход (проходим)
        '.' - асфальт (вес 1) - то же, что пробел
        ',' - песок (вес 2)
        '~' - болото (вес 3)

    Лишние пробельные символы в начале/конце файла игнорируются,
    но внутри строки пробелы значимы (это проходы).
    """

    WEIGHT_MAP = {'.': 1, ',': 2, '~': 3}

    def build_from_file(self, filename) -> Maze:
        with open(filename, encoding="utf-8") as f:
            raw = f.read().splitlines()

        # отбрасываем пустые строки в конце - частая мелочь
        while raw and raw[-1] == "":
            raw.pop()
        if not raw:
            raise ValueError(f"Файл лабиринта {filename!r} пуст.")

        height = len(raw)
        width = max(len(line) for line in raw)

        # выравниваем строки по ширине пробелами (если строки разной длины)
        lines = [line.ljust(width, '#') for line in raw]

        maze = Maze(width, height)
        start_count = 0
        exit_count = 0

        for y, line in enumerate(lines):
            for x, ch in enumerate(line):
                cell = self._parse_char(x, y, ch)
                maze.grid[y][x] = cell
                if cell.is_start:
                    maze.start = cell
                    start_count += 1
                if cell.is_exit:
                    maze.exit_ = cell
                    exit_count += 1

        # валидация
        if start_count != 1:
            raise ValueError(
                f"В лабиринте {filename!r} ожидался ровно 1 'S', нашли {start_count}.")
        if exit_count != 1:
            raise ValueError(
                f"В лабиринте {filename!r} ожидался ровно 1 'E', нашли {exit_count}.")

        return maze

    def _parse_char(self, x, y, ch):
        if ch == '#':
            return Cell(x, y, is_wall=True)
        if ch == 'S':
            return Cell(x, y, is_start=True, weight=1)
        if ch == 'E':
            return Cell(x, y, is_exit=True, weight=1)
        if ch in self.WEIGHT_MAP:
            return Cell(x, y, weight=self.WEIGHT_MAP[ch])
        if ch == ' ':
            return Cell(x, y, weight=1)
        raise ValueError(f"Неизвестный символ {ch!r} в позиции ({x},{y}).")
