# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""output_filter.py — Stop-Hook.

Liest die letzte Assistant-Nachricht aus payload.transcript_path und scannt sie
gegen zwei Listen:

1. Umlaut-Ersatz-Whitelist: deutsche Wörter, die fälschlich als ae/oe/ue/ss
   geschrieben wurden (`fuer` statt `für`, `koennen` statt `können`,
   `gemaess` statt `gemäß`). Whole-word-Match, case-insensitive,
   Korrektur-Vorschlag wird mitgeliefert.

2. KI-Sprech-Phrasen: typische Floskeln und Template-Einleitungen, die
   Texte unmenschlich klingen lassen.

Befunde werden auf stderr ausgegeben (sichtbar im Claude-Code-UI). Der Hook
gibt immer `{"action": "allow"}` zurück — er kann den bereits emittierten
Output nicht nachträglich umschreiben (siehe Architektur-Spec 8.5,
Inline-Klarstellung Stop-Hook-Output, Phase 5 Cluster T). Block-Variante
ist Welle-2-Backlog.

Erstinstall-fest: fehlendes oder unlesbares Transcript → allow, kein Crash.
"""
import json
import re
import sys
from pathlib import Path


# Whitelist deutscher Wörter, deren ae/oe/ue/ss-Form eindeutig ein Umlaut-Ersatz
# ist. Whole-word-Match verhindert False-Positives auf englische Wörter (`take`,
# `force`, `queue`, `process`).
UMLAUT_REPLACEMENTS: dict[str, str] = {
    "fuer": "für",
    "dafuer": "dafür",
    "wofuer": "wofür",
    "hierfuer": "hierfür",
    "zurueck": "zurück",
    "frueh": "früh",
    "frueher": "früher",
    "fruehzeitig": "frühzeitig",
    "darueber": "darüber",
    "worueber": "worüber",
    "ueber": "über",
    "ueberhaupt": "überhaupt",
    "uebersicht": "übersicht",
    "uebernehmen": "übernehmen",
    "ueblich": "üblich",
    "koennen": "können",
    "koennte": "könnte",
    "koennten": "könnten",
    "muessen": "müssen",
    "muesste": "müsste",
    "wuerde": "würde",
    "wuerden": "würden",
    "schoen": "schön",
    "groesse": "größe",
    "groesser": "größer",
    "loesung": "lösung",
    "loesungen": "lösungen",
    "aenderung": "änderung",
    "aenderungen": "änderungen",
    "pruefung": "prüfung",
    "pruefen": "prüfen",
    "moeglich": "möglich",
    "moeglichkeit": "möglichkeit",
    "naechst": "nächst",
    "naechste": "nächste",
    "naechsten": "nächsten",
    "waehlen": "wählen",
    "waehrend": "während",
    "haeufig": "häufig",
    "faehigkeit": "fähigkeit",
    "faehigkeiten": "fähigkeiten",
    "gemaess": "gemäß",
    "grosse": "große",
    "strasse": "straße",
    "spaeter": "später",
    "spaet": "spät",
    "hoeren": "hören",
    "ungefaehr": "ungefähr",
    "gespraech": "gespräch",
    "regelmaessig": "regelmäßig",
    "uebrigens": "übrigens",
    "ueblicherweise": "üblicherweise",
    # Compound-Wörter mit ueber-Präfix (f.9 / Audit B-HIGH / I-H2): die
    # bisherige Liste hatte nur `ueber` als ganzes Wort, fing aber keine
    # Compounds wie übernommen/übergeben/überschreiben.
    "uebernommen": "übernommen",
    "uebergeben": "übergeben",
    "uebergebenen": "übergebenen",
    "ueberschreiben": "überschreiben",
    "ueberschrieben": "überschrieben",
    "ueberspringen": "überspringen",
    "ueberlegen": "überlegen",
    "uebertragen": "übertragen",
    "uebertraegt": "überträgt",
    # Compound-Wörter mit fuegen-Suffix (f.8 / I-H2): bisherige Liste hatte
    # keine Compounds wie hinzufügen/einfügen.
    "hinzufuegen": "hinzufügen",
    "einfuegen": "einfügen",
    "ausfuegen": "ausfügen",
    "zusammenfuegen": "zusammenfügen",
}


# Typische KI-Sprech-Phrasen. Case-insensitive Substring-Match.
KI_SPRECH_PHRASES: list[str] = [
    "in der heutigen schnelllebigen welt",
    "es ist wichtig zu beachten",
    "darüber hinaus",
    "im wesentlichen",
    "letztendlich",
    "es ist erwähnenswert",
    "tauchen wir ein",
    "lassen sie uns",
    "in diesem artikel",
    "zusammenfassend lässt sich sagen",
    "es lässt sich nicht leugnen",
    "es ist kein geheimnis",
    "wie wir alle wissen",
    "in der modernen welt",
]


def _scan_umlauts(text: str) -> list[dict]:
    findings: list[dict] = []
    for ascii_form, correct in UMLAUT_REPLACEMENTS.items():
        pattern = re.compile(rf"\b{re.escape(ascii_form)}\b", re.IGNORECASE)
        for m in pattern.finditer(text):
            findings.append(
                {
                    "kind": "umlaut",
                    "match": m.group(0),
                    "suggestion": correct,
                    "position": m.start(),
                }
            )
    return findings


def _scan_ki_sprech(text: str) -> list[dict]:
    findings: list[dict] = []
    lower = text.lower()
    for phrase in KI_SPRECH_PHRASES:
        start = 0
        while True:
            idx = lower.find(phrase, start)
            if idx == -1:
                break
            findings.append(
                {
                    "kind": "ki-sprech",
                    "match": text[idx : idx + len(phrase)],
                    "position": idx,
                }
            )
            start = idx + len(phrase)
    return findings


def scan(text: str) -> list[dict]:
    """Scannt einen Text und liefert eine sortierte Trefferliste."""
    findings = _scan_umlauts(text) + _scan_ki_sprech(text)
    findings.sort(key=lambda f: f["position"])
    return findings


def _last_assistant_message(transcript_path: Path) -> str | None:
    """Liest die letzte Assistant-Nachricht aus einer JSONL-Transcript-Datei.

    Bei Encoding-Fehlern wird ein Hinweis auf stderr geschrieben — sonst
    bleibt das Problem unsichtbar (silent allow maskiert Konfig-Drift).
    """
    try:
        raw = transcript_path.read_bytes()
    except OSError:
        return None
    try:
        text = raw.decode("utf-8")
    except UnicodeDecodeError:
        sys.stderr.write(
            f"output_filter: Transcript {transcript_path} ist nicht UTF-8;"
            " Scan übersprungen.\n"
        )
        return None
    lines = text.splitlines()

    for line in reversed(lines):
        line = line.strip()
        if not line:
            continue
        try:
            entry = json.loads(line)
        except json.JSONDecodeError:
            continue
        if entry.get("role") != "assistant" and entry.get("type") != "assistant":
            continue
        # Reihenfolge der Felder ist deterministisch — leerer String IST eine
        # gültige (leere) Nachricht, kein Fall-Through auf ältere Antworten.
        for key in ("content", "text", "message"):
            if key in entry:
                content = entry[key]
                break
        else:
            continue
        if isinstance(content, str):
            return content
        if isinstance(content, list):
            parts = [
                item.get("text", "")
                for item in content
                if isinstance(item, dict) and item.get("type") == "text"
            ]
            return "".join(parts) or None
        if isinstance(content, dict):
            inner = content.get("content")
            if isinstance(inner, str):
                return inner
            if isinstance(inner, list):
                parts = [
                    item.get("text", "")
                    for item in inner
                    if isinstance(item, dict) and item.get("type") == "text"
                ]
                return "".join(parts) or None
    return None


def _format_findings(findings: list[dict]) -> str:
    umlauts = [f for f in findings if f["kind"] == "umlaut"]
    ki = [f for f in findings if f["kind"] == "ki-sprech"]
    parts: list[str] = ["output_filter — Befunde in der letzten Antwort:"]
    if umlauts:
        parts.append(f"  Umlaut-Ersatz ({len(umlauts)} Treffer):")
        for f in umlauts:
            parts.append(f"    · `{f['match']}` → `{f['suggestion']}`")
    if ki:
        parts.append(f"  KI-Sprech-Phrasen ({len(ki)} Treffer):")
        for f in ki:
            parts.append(f"    · „{f['match']}\"")
    parts.append(
        "  Hinweis: Stop-Hook kann den Output nicht nachträglich ändern — bitte"
        " in der nächsten Runde korrigieren."
    )
    return "\n".join(parts)


def main():
    """F14: UTF-8 für stdout/stderr erzwungen (Windows-cp1252-Default).
    Findings-Liste enthält deutsche Umlaute (Korrektur-Vorschläge wie
    'koennen' -> 'können') und KI-Sprech-Zitate.
    """
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")
    sys.stderr.reconfigure(encoding="utf-8", errors="replace")
    try:
        payload = json.loads(sys.stdin.read())
    except json.JSONDecodeError:
        print(json.dumps({"action": "allow"}))
        return

    transcript = payload.get("transcript_path")
    if not transcript:
        print(json.dumps({"action": "allow"}))
        return

    text = _last_assistant_message(Path(transcript))
    if not text:
        print(json.dumps({"action": "allow"}))
        return

    findings = scan(text)
    if findings:
        sys.stderr.write(_format_findings(findings) + "\n")

    print(json.dumps({"action": "allow"}, ensure_ascii=False))


if __name__ == "__main__":
    main()
