from __future__ import annotations

import random
from collections import deque
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
MAZE_DIR = ROOT / "data" / "mazes"


def main() -> None:
    generate_all()


def generate_all() -> None:
    MAZE_DIR.mkdir(parents=True, exist_ok=True)
    _write("small.txt", _small_maze())
    _write("medium.txt", _perfect_maze(50, 50, seed=2026))
    _write("large.txt", _perfect_maze(100, 100, seed=2027))
    _write("empty.txt", _empty_maze(50, 50))
    _write("no_exit.txt", _no_path_maze(30, 30))


def _write(filename: str, rows: list[str]) -> None:
    (MAZE_DIR / filename).write_text("\n".join(rows) + "\n", encoding="utf-8")


def _small_maze() -> list[str]:
    return [
        "##########",
        "#S     #E#",
        "# #### # #",
        "# #    # #",
        "# # #### #",
        "# #      #",
        "# ###### #",
        "#        #",
        "######## #",
        "##########",
    ]


def _empty_maze(width: int, height: int) -> list[str]:
    grid = _bordered_grid(width, height, fill=" ")
    grid[1][1] = "S"
    grid[height - 2][width - 2] = "E"
    return _to_rows(grid)


def _no_path_maze(width: int, height: int) -> list[str]:
    grid = [["#" for _ in range(width)] for _ in range(height)]
    grid[1][1] = "S"
    grid[height - 2][width - 2] = "E"
    return _to_rows(grid)


def _perfect_maze(width: int, height: int, seed: int) -> list[str]:
    if width < 5 or height < 5:
        raise ValueError("Maze must be at least 5x5")

    randomizer = random.Random(seed)
    grid = [["#" for _ in range(width)] for _ in range(height)]
    start = (1, 1)
    stack = [start]
    grid[start[1]][start[0]] = " "

    while stack:
        x, y = stack[-1]
        candidates = []
        for dx, dy in ((0, -2), (2, 0), (0, 2), (-2, 0)):
            nx, ny = x + dx, y + dy
            if 1 <= nx < width - 1 and 1 <= ny < height - 1 and grid[ny][nx] == "#":
                candidates.append((nx, ny, dx, dy))

        if not candidates:
            stack.pop()
            continue

        nx, ny, dx, dy = randomizer.choice(candidates)
        grid[y + dy // 2][x + dx // 2] = " "
        grid[ny][nx] = " "
        stack.append((nx, ny))

    exit_x, exit_y = _farthest_open_cell(grid, start)
    grid[start[1]][start[0]] = "S"
    grid[exit_y][exit_x] = "E"
    return _to_rows(grid)


def _farthest_open_cell(grid: list[list[str]], start: tuple[int, int]) -> tuple[int, int]:
    queue = deque([start])
    distances = {start: 0}
    farthest = start

    while queue:
        x, y = queue.popleft()
        if distances[(x, y)] > distances[farthest]:
            farthest = (x, y)

        for dx, dy in ((0, -1), (1, 0), (0, 1), (-1, 0)):
            nx, ny = x + dx, y + dy
            if (nx, ny) not in distances and grid[ny][nx] != "#":
                distances[(nx, ny)] = distances[(x, y)] + 1
                queue.append((nx, ny))

    return farthest


def _bordered_grid(width: int, height: int, fill: str) -> list[list[str]]:
    grid = [[fill for _ in range(width)] for _ in range(height)]
    for x in range(width):
        grid[0][x] = "#"
        grid[height - 1][x] = "#"
    for y in range(height):
        grid[y][0] = "#"
        grid[y][width - 1] = "#"
    return grid


def _to_rows(grid: list[list[str]]) -> list[str]:
    return ["".join(row) for row in grid]


if __name__ == "__main__":
    main()
