"""Deterministisches Lesbarkeits-Gate für Guide-Sections.

Ein kleines deutsches Komplexitäts-Modell (DistilBERT, GermEval 2022, Skala 1–7)
bewertet die Verständlichkeit der Fließtext-Prosa. guide.py meldet zu schwere
Sections in die bestehende Lese-Prüfungs-/Überarbeitungs-Schleife — kein Prompt,
kein Raten.

Optional: fehlen `transformers`/`torch` oder lädt das Modell nicht, ist das Gate
stumm deaktiviert (das Backend läuft unverändert weiter). CPU genügt; der Aufrufer
wrappt die Bewertung in `asyncio.to_thread` (blockierende Modell-Inferenz).
"""

import logging
import re

from config import (
    LESBARKEIT_AKTIV, LESBARKEIT_HART, LESBARKEIT_HART_ANTEIL, LESBARKEIT_MAX, LESBARKEIT_MODELL,
)

log = logging.getLogger("creator.lesbarkeit")

_modell_cache = None      # (tokenizer, model, torch) — Singleton
_ladeversuch = False      # schon versucht zu laden?

# Markup raus → reiner Fließtext (Code zählt nicht zur Lesbarkeit).
_CODE_FENCE = re.compile(r"```.*?```", re.DOTALL)
_COMMENT = re.compile(r"<!--.*?-->", re.DOTALL)
_INLINE_CODE = re.compile(r"`[^`]*`")
_LINK = re.compile(r"\[([^\]]*)\]\([^)]*\)")
_MD_MARK = re.compile(r"^[ \t]*([#>]+|[-*+]\s)|[*_~|]", re.MULTILINE)
_WS = re.compile(r"\s+")
_SATZ = re.compile(r"(?<=[.!?])\s+")


def _modell():
    """Lädt das Modell einmalig. None = Gate aus (deaktiviert oder Lade-Fehler)."""
    global _modell_cache, _ladeversuch
    if _ladeversuch:
        return _modell_cache
    _ladeversuch = True
    if not LESBARKEIT_AKTIV:
        return None
    try:
        import torch
        from transformers import AutoModelForSequenceClassification, AutoTokenizer
        tok = AutoTokenizer.from_pretrained(LESBARKEIT_MODELL)
        model = AutoModelForSequenceClassification.from_pretrained(LESBARKEIT_MODELL)
        model.eval()
        _modell_cache = (tok, model, torch)
        log.info("Lesbarkeits-Modell geladen: %s (num_labels=%d)", LESBARKEIT_MODELL, model.config.num_labels)
    except Exception as e:
        log.warning("Lesbarkeits-Gate deaktiviert (Modell nicht ladbar): %s", e)
        _modell_cache = None
    return _modell_cache


def _prosa(md: str) -> str:
    """Markdown/Code strippen → reiner Fließtext für die Bewertung."""
    t = _CODE_FENCE.sub(" ", md)
    t = _COMMENT.sub(" ", t)
    t = _INLINE_CODE.sub(" ", t)
    t = _LINK.sub(r"\1", t)
    t = _MD_MARK.sub(" ", t)
    return _WS.sub(" ", t).strip()


def _saetze(text: str) -> list[str]:
    """Fließtext in Sätze splitten; sehr kurze Fragmente verwerfen."""
    return [s.strip() for s in _SATZ.split(text) if len(s.strip()) >= 15]


def _scores(saetze: list[str]) -> list[float]:
    """Komplexität je Satz (1–7). Regression (num_labels=1) oder Erwartungswert über Klassen."""
    tok, model, torch = _modell_cache
    werte: list[float] = []
    n = model.config.num_labels
    for i in range(0, len(saetze), 16):
        batch = saetze[i:i + 16]
        enc = tok(batch, return_tensors="pt", truncation=True, max_length=256, padding=True)
        with torch.no_grad():
            logits = model(**enc).logits
        if n == 1:
            vals = logits.reshape(-1).tolist()
        else:
            probs = torch.softmax(logits, dim=-1)
            stufen = torch.arange(1, n + 1, dtype=probs.dtype)
            vals = (probs * stufen).sum(-1).reshape(-1).tolist()
        werte.extend(vals)
    return werte


def bewerte_sections(md_by_num: dict[int, str]) -> dict[int, str]:
    """{num: section_md} → {num: Hinweis} nur für zu schwere Sections.

    Leeres dict, wenn das Gate aus ist. Blockierend (CPU) — in to_thread aufrufen.
    """
    if _modell() is None:
        return {}
    out: dict[int, str] = {}
    for num, md in md_by_num.items():
        saetze = _saetze(_prosa(md or ""))
        if len(saetze) < 2:  # fast nur Code / zu kurz → überspringen
            continue
        werte = _scores(saetze)
        if not werte:
            continue
        schnitt = sum(werte) / len(werte)
        hart = sum(1 for w in werte if w > LESBARKEIT_HART) / len(werte)
        # Zu schwer = hoher Schnitt ODER zu viele harte Einzelsätze (Ausreißer-Nester).
        if schnitt > LESBARKEIT_MAX or hart >= LESBARKEIT_HART_ANTEIL:
            out[num] = (
                f"Zu schwer lesbar (Ø {schnitt:.1f}/7, {hart * 100:.0f}% harte Sätze): "
                "kürzere Sätze, einfachere Wörter, weniger Schachtelsätze, mehr Beispiele."
            )
    return out
