from __future__ import annotations

from abc import ABC, abstractmethod
from pathlib import Path

from .models import Cell, Maze


class MazeBuilder(ABC):
    @abstractmethod
    def build_from_file(self, filename: str | Path) -> Maze:
        raise NotImplementedError

    def buildFromFile(self, filename: str | Path) -> Maze:
        return self.build_from_file(filename)


class TextFileMazeBuilder(MazeBuilder):
    WALL = "#"
    START = "S"
    EXIT = "E"
    PASSAGES = {" ", "."}
    WEIGHTS = {"1": 1, "2": 2, "3": 3, "~": 3}

    def build_from_file(self, filename: str | Path) -> Maze:
        path = Path(filename)
        rows = path.read_text(encoding="utf-8").splitlines()

        if not rows:
            raise ValueError(f"Maze file is empty: {path}")

        width = len(rows[0])
        if width == 0:
            raise ValueError("Maze width must be greater than zero")
        if any(len(row) != width for row in rows):
            raise ValueError("All maze rows must have the same width")

        cells: list[list[Cell]] = []
        start: Cell | None = None
        exit: Cell | None = None

        for y, row in enumerate(rows):
            cell_row: list[Cell] = []
            for x, char in enumerate(row):
                cell = self._create_cell(x, y, char)
                if cell.is_start:
                    if start is not None:
                        raise ValueError("Maze must contain exactly one start cell")
                    start = cell
                if cell.is_exit:
                    if exit is not None:
                        raise ValueError("Maze must contain exactly one exit cell")
                    exit = cell
                cell_row.append(cell)
            cells.append(cell_row)

        if start is None:
            raise ValueError("Maze must contain a start cell marked with 'S'")
        if exit is None:
            raise ValueError("Maze must contain an exit cell marked with 'E'")

        return Maze(cells, start, exit)

    def _create_cell(self, x: int, y: int, char: str) -> Cell:
        if char == self.WALL:
            return Cell(x=x, y=y, is_wall=True, symbol=char)
        if char == self.START:
            return Cell(x=x, y=y, is_start=True, symbol=char)
        if char == self.EXIT:
            return Cell(x=x, y=y, is_exit=True, symbol=char)
        if char in self.PASSAGES:
            return Cell(x=x, y=y, symbol=" ")
        if char in self.WEIGHTS:
            return Cell(x=x, y=y, weight=self.WEIGHTS[char], symbol=char)
        raise ValueError(f"Unsupported maze symbol {char!r} at ({x}, {y})")
