"""Template Parser für die PBI Documentation by Oliver Gropp.

Liest die Dokumentationsvorlage ein und identifiziert alle
{{...}}-Platzhalter mit Position, Zeilennummer und Kapitel-Zuordnung.
"""
from __future__ import annotations

import re

from core.models import Placeholder, ParseResult, TemplateParseError

# Regex für {{...}}-Platzhalter
_PLACEHOLDER_RE = re.compile(r"\{\{(.+?)\}\}")

# Regex für Markdown-Abschnittsüberschriften (## ...)
_SECTION_RE = re.compile(r"^##\s+(.+)$", re.MULTILINE)


class TemplateParser:
    """Parser für Markdown-Dokumentationsvorlagen mit {{...}}-Platzhaltern."""

    def parse(self, template_path: str) -> ParseResult:
        """Liest Vorlage ein und gibt alle Platzhalter zurück.

        Args:
            template_path: Pfad zur Markdown-Vorlagendatei.

        Returns:
            ParseResult mit Platzhaltern, Template-Inhalt und Abschnitten.

        Raises:
            TemplateParseError: Wenn die Datei nicht gelesen werden kann
                oder keine Platzhalter gefunden wurden.
        """
        try:
            with open(template_path, encoding="utf-8") as f:
                content = f.read()
        except OSError as exc:
            raise TemplateParseError(
                f"Vorlage kann nicht gelesen werden: {template_path} – {exc}"
            ) from exc

        placeholders = self.extract_placeholders(content)

        if not placeholders:
            raise TemplateParseError(
                f"Keine Platzhalter in der Vorlage gefunden: {template_path}"
            )

        sections = self._extract_sections(content)

        return ParseResult(
            placeholders=placeholders,
            template_content=content,
            sections=sections,
        )

    def extract_placeholders(self, content: str) -> list[Placeholder]:
        """Extrahiert alle {{...}}-Platzhalter aus dem Inhalt.

        Args:
            content: Der Markdown-Text der Vorlage.

        Returns:
            Liste aller gefundenen Placeholder-Objekte.
        """
        # Abschnittsgrenzen vorberechnen für schnelle Zuordnung
        section_boundaries = self._build_section_boundaries(content)
        lines = content.split("\n")
        # Startposition jeder Zeile im Gesamttext vorberechnen
        line_starts: list[int] = []
        pos = 0
        for line in lines:
            line_starts.append(pos)
            pos += len(line) + 1  # +1 für das \n

        placeholders: list[Placeholder] = []
        for match in _PLACEHOLDER_RE.finditer(content):
            name = match.group(1).strip()
            position = match.start()

            # Zeilennummer bestimmen (1-basiert)
            line_number = content[:position].count("\n") + 1

            # Aktuellen Abschnitt bestimmen
            section = self._get_section_at(position, section_boundaries)

            # Kontext: die Zeile, in der der Platzhalter steht
            context = lines[line_number - 1].strip() if line_number <= len(lines) else ""

            placeholders.append(
                Placeholder(
                    name=name,
                    position=position,
                    line=line_number,
                    section=section,
                    context=context,
                )
            )

        return placeholders

    # ------------------------------------------------------------------
    # Hilfsmethoden
    # ------------------------------------------------------------------

    @staticmethod
    def _extract_sections(content: str) -> list[str]:
        """Extrahiert alle ## Abschnittsüberschriften aus dem Markdown."""
        return [m.group(1).strip() for m in _SECTION_RE.finditer(content)]

    @staticmethod
    def _build_section_boundaries(content: str) -> list[tuple[int, str]]:
        """Erstellt eine sortierte Liste von (Position, Abschnittsname).

        Wird verwendet, um für eine beliebige Zeichenposition den
        zugehörigen Abschnitt zu bestimmen.
        """
        boundaries: list[tuple[int, str]] = []
        for m in _SECTION_RE.finditer(content):
            boundaries.append((m.start(), m.group(1).strip()))
        return boundaries

    @staticmethod
    def _get_section_at(
        position: int, boundaries: list[tuple[int, str]]
    ) -> str:
        """Gibt den Abschnittsnamen zurück, der an *position* gilt."""
        current_section = ""
        for sec_pos, sec_name in boundaries:
            if sec_pos > position:
                break
            current_section = sec_name
        return current_section
