from __future__ import annotations

import re
from dataclasses import dataclass, field

from .fen import parse_fen
from .utils import Move


@dataclass
class PGNOptions:
    max_width: int = 6
    newline: str = "\n"
    with_comments: bool = False


@dataclass
class ParsedPGN:
    moves: list[str] = field(default_factory=list)
    comments: dict[int, str] = field(default_factory=dict)


def encode_pgn(
    history: list[Move],
    opts: PGNOptions | None = None,
    comments: dict[str, str] | None = None,
) -> str:
    options = opts or PGNOptions()
    comment_map = comments or {}

    pgn = ""
    prev_move_no = 0
    for index, move in enumerate(history):
        state = parse_fen(move.before)
        if state.move_number != prev_move_no:
            indicator = "." if move.color == "w" else "..."
            pgn += f"{state.move_number}{indicator} "
            prev_move_no = state.move_number

        pgn += f"{move.san} "

        if options.with_comments and move.after in comment_map:
            pgn += f"{{{comment_map[move.after]}}} "

        if (index + 1) % options.max_width == 0:
            pgn += options.newline

    return pgn.replace("  ", " ").strip()


def parse_pgn(
    pgn: str,
    opts: PGNOptions | None = None,
    newline: str | None = None,
) -> ParsedPGN:
    line_separator = newline if newline is not None else (opts.newline if opts else "\n")

    comments: dict[int, str] = {}
    moves: list[str] = []
    normalized = re.sub(r"\d+\.+", "", " ".join(pgn.split(line_separator))).strip()

    while normalized:
        normalized = normalized.strip()
        if not normalized:
            break

        if normalized.startswith("{"):
            end_idx = normalized.find("}")
            if end_idx != -1:
                comment = normalized[1:end_idx].strip()
                if moves and comment:
                    comments[len(moves) - 1] = comment
                normalized = normalized[end_idx + 1 :]
                continue

        match = re.match(r"^(\S+)", normalized)
        if not match:
            break

        move = match.group(1)
        if move and not move.startswith("{"):
            moves.append(move)
        normalized = normalized[len(move) :]

    return ParsedPGN(moves=moves, comments=comments)


# JS-style aliases
encodePGN = encode_pgn
parsePGN = parse_pgn
ParsedPGNType = ParsedPGN
