"""Parser for the UMC mapcycle KeyValues format into a nested dict."""

import re
from pathlib import Path
from typing import Any

from core.exceptions import UMCParserError

# ==========================================================================
# 1. OUTILS PURS DE TRAITEMENT TEXTUEL (Responsabilité : Analyse de chaînes)
# ==========================================================================


def _is_single_token(line: str) -> bool:
    """Check whether the line contains only one token enclosed in quotation marks (e.g., "category_name")."""
    return line.startswith('"') and line.endswith('"') and line.count('"') == 2


def _extract_token(line: str) -> str:
    """Extracts the raw value of a unique token."""
    return line.strip('"')


def _is_key_value(line: str) -> bool:
    """Check whether the line is a standard key-value pair (contains 4 quotation marks)."""
    return line.count('"') == 4


def _extract_key_value(line: str) -> tuple[str, str]:
    """Splits and returns the key-value pair from a KeyValues line."""
    parts = line.split('"')
    return parts[1], parts[3]


def _prepare_lines(file_path: Path) -> list[str]:
    """Reads a file, standardizes the formatting, and filters out comments and blank lines."""
    with file_path.open(encoding="utf-8") as f:
        content = f.read()

    content = re.sub(r'(".*?")\s*\{\s*\}', r"\1\n{\n}", content)
    content = re.sub(r'(".*?")\s*\{', r"\1\n{", content)
    content = content.replace("}", "\n}\n")

    lines = []
    for line in content.splitlines():
        line_strip = line.strip()
        if not line_strip or line_strip.startswith("//"):
            continue
        lines.append(line_strip)
    return lines


# ==========================================================================
# 2. MACHINE À ÉTATS DE PARSING (Responsabilité : Maintien du contexte d'I/O)
# ==========================================================================


class _UMCStateParser:
    """A private parser that manages the state of the KeyValues tree as it is parsed."""

    def __init__(self) -> None:
        self._stack: list[str] = []
        self._current_category: str | None = None
        self._current_map: str | None = None
        self._pending_name: str | None = None
        self._result: dict[str, Any] = {}

    def execute(self, lines: list[str]) -> dict[str, Any]:
        """Iterates through the pre-filtered rows and builds the final data tree."""
        for line in lines:
            if line == "{":
                self._handle_open_brace()
            elif line == "}":
                self._handle_close_brace()
            elif _is_key_value(line):
                self._handle_key_value(line)
            elif _is_single_token(line):
                self._pending_name = _extract_token(line)

        return self._result

    def _handle_open_brace(self) -> None:
        """Handles opening a block by pushing the current context onto the stack."""
        if not self._pending_name:
            return

        self._stack.append(self._pending_name)
        level = len(self._stack)

        if level == 1:  # "umc_mapcycle" (la racine)
            pass
        elif level == 2:  # Niveau Catégorie
            self._current_category = self._pending_name
            self._result[self._current_category] = {"options": {}, "maps": {}}
        elif level == 3:  # Niveau Map
            if not self._current_category:
                msg = "Structure invalide : Map detected outside a category."
                raise UMCParserError(msg)
            self._current_map = self._pending_name
            self._result[self._current_category]["maps"][self._current_map] = {}

        self._pending_name = None

    def _handle_close_brace(self) -> None:
        """Handles the closure of a block by unstacking the context."""
        if not self._stack:
            msg = "Corrupted file structure: Orphaned closing brace."
            raise UMCParserError(msg)

        self._stack.pop()
        level = len(self._stack)

        if level == 1:
            self._current_category = None
        elif level == 2:
            self._current_map = None

    def _handle_key_value(self, line: str) -> None:
        """Assigns the key/value in the in-memory dictionary based on the active context."""
        key, value = _extract_key_value(line)

        if self._current_map is not None and self._current_category is not None:
            self._result[self._current_category]["maps"][self._current_map][key] = value
        elif self._current_category is not None:
            self._result[self._current_category]["options"][key] = value
        else:
            msg = f"Orphan option detected outside a valid block : {key}={value}"
            raise UMCParserError(msg)


# ==========================================================================
# 3. POINT D'ENTRÉE PUBLIC (Responsabilité : API propre pour l'application)
# ==========================================================================
def parse_umc_file(file_path: str | Path) -> dict[str, Any]:
    """Parses a UMC configuration file in KeyValues format and returns its dictionary."""
    target_path = Path(file_path)
    if not target_path.exists():
        msg = f"UMC file not found : {target_path}"
        raise FileNotFoundError(msg)

    lines = _prepare_lines(target_path)

    parser_machine = _UMCStateParser()
    return parser_machine.execute(lines)
