"""Gibt Fragen des amtlichen Katalogs lesbar aus – mit Lösung.

Gedacht für die Arbeit an den Erklärungstexten: Man braucht die Frage, die
markierten richtigen Antworten und – bei offenen Fragen – die Musterantwort
des Bundesverwaltungsamtes an einer Stelle.

Aufrufe
-------
Ein Abschnitt, ausschnittweise::

    python data-pipeline/fragen.py --abschnitt I.1 --von 0 --anzahl 10

Ein ganzes Kapitel ohne Abschnittsgliederung::

    python data-pipeline/fragen.py --kapitel II --von 0 --anzahl 10

Einzelne Fragen::

    python data-pipeline/fragen.py --id I.1-04 I.2-01

Nur die Kennungen (etwa zum Aufteilen in Arbeitspakete)::

    python data-pipeline/fragen.py --abschnitt I.1 --nur-ids

Wortformen im Katalog zählen – für die Arbeit am Glossar::

    python data-pipeline/fragen.py --wortform führt geführt Führens

Zeigt zusätzlich an, ob zu einer Frage bereits eine Erklärung vorliegt.
"""

from __future__ import annotations

import argparse
import io
import json
import re
import sys
from pathlib import Path

WURZEL = Path(__file__).resolve().parent.parent
KATALOG = WURZEL / 'content' / 'katalog' / 'katalog.json'
ERKLAERUNGEN = WURZEL / 'content' / 'erklaerungen.json'
ALTTEXTE = WURZEL / 'content' / 'alttexte.json'


def vorhandene_erklaerungen() -> set[str]:
    if not ERKLAERUNGEN.exists():
        return set()
    daten = json.load(io.open(ERKLAERUNGEN, encoding='utf-8'))
    return set(daten.get('zuFrage') or {})


def alttexte() -> dict[str, str]:
    """Bild-ID -> Beschreibung.

    Die Datei bildet Bild-ID auf `{alt, beschreibung, hinweis}` ab. Fuer die
    Arbeit an den Erklaerungen ist alles drei nuetzlich: `alt` ist der
    ausgelieferte Alternativtext, `beschreibung` die erklaerende Bedeutung,
    die die Anwendung erst nach dem Beantworten nachreicht, `hinweis` die
    interne Notiz zur Herkunft der Formulierung. Bei den Fragen, in denen die
    Zeichen selbst die Antwortoptionen sind, verschweigt `alt` die Bedeutung
    bewusst - dann tragen sie die beiden anderen Felder.
    """
    if not ALTTEXTE.exists():
        return {}
    daten = json.load(io.open(ALTTEXTE, encoding='utf-8'))
    ergebnis: dict[str, str] = {}
    for schluessel, wert in daten.items():
        if schluessel.startswith('_'):
            continue  # Kopfzeilen der Datei, kein Bild
        if isinstance(wert, str):
            ergebnis[schluessel] = wert
        else:
            teile = [wert.get('alt', '')]
            if wert.get('beschreibung'):
                teile.append(f'Bedeutung: {wert["beschreibung"]}')
            if wert.get('hinweis'):
                teile.append(f'intern: {wert["hinweis"]}')
            ergebnis[schluessel] = ' | '.join(t for t in teile if t)
    return ergebnis


#: Zeichen, die in einem deutschen Wort vorkommen – wie in shared/glossar.ts.
#: `` taugt nicht: Es kennt nur ASCII und saehe in „Schiessstaette" an jedem
#: Umlaut eine Wortgrenze.
WORTZEICHEN = 'A-Za-zÄÖÜäöüßẞ0-9'


def katalogkorpus(katalog: dict) -> str:
    """Fragen, Antwortmoeglichkeiten und Musterantworten als ein Text."""
    teile: list[str] = []
    for frage in katalog['fragen']:
        teile.append(frage['frage']['text'])
        muster = frage.get('musterantwort')
        if muster:
            teile.append(muster['text'])
        for option in frage.get('optionen') or []:
            teile.append(option['inhalt']['text'])
    return ' '.join(teile)


def erklaerungskorpus() -> str:
    """Die Erklaerungstexte als ein Text.

    Abkuerzungen wie WBK, NWR oder VDMA kommen im amtlichen Katalog gar nicht
    vor - sehr wohl aber in den Erklaerungen, die die Anwendung anzeigt. Fuer
    WCAG 3.1.4 zaehlt, was der Nutzer liest, nicht nur der Fragenwortlaut.
    """
    if not ERKLAERUNGEN.exists():
        return ''
    daten = json.load(io.open(ERKLAERUNGEN, encoding='utf-8'))
    teile: list[str] = []
    for eintrag in (daten.get('zuFrage') or {}).values():
        teile.append(eintrag.get('kurz', ''))
        teile.append(eintrag.get('text', ''))
        teile.append(eintrag.get('merksatz', '') or '')
    return ' '.join(teile)


def wortform_suchen(formen: list[str], katalog: dict) -> int:
    """Zaehlt, wie oft Wortformen als eigenes Wort vorkommen.

    Durchsucht werden Katalog UND Erklaerungen. Gesucht wird mit derselben
    Wortgrenze wie in der Anwendung.
    """
    korpus = katalogkorpus(katalog) + ' ' + erklaerungskorpus()

    for form in formen:
        ausdruck = rf'(?<![{WORTZEICHEN}]){re.escape(form)}(?![{WORTZEICHEN}])'
        treffer = list(re.finditer(ausdruck, korpus, re.IGNORECASE))
        print(f'{len(treffer):5}  {form!r}')
        for fund in treffer[:2]:
            anfang = max(0, fund.start() - 70)
            print(f'       …{korpus[anfang:fund.end() + 70]}…')
    return 0


def frage_zeigen(frage: dict, bilder: dict[str, str], schon_da: set[str]) -> None:
    marke = ' [Erklärung liegt vor]' if frage['id'] in schon_da else ''
    ort = frage['kapitel'] + (f' / {frage["abschnitt"]}' if frage.get('abschnitt') else '')
    print(f'### {frage["id"]}  (amtliche Nr. {frage["amtliche_nummer"]}, {ort}, '
          f'Typ {frage["typ"]}, Katalogseite {frage["seite"]}){marke}')
    print(f'FRAGE: {frage["frage"]["text"]}')

    for bild_id in frage.get('bilder') or []:
        beschreibung = bilder.get(bild_id, '(kein Alternativtext hinterlegt)')
        print(f'  [Abbildung zur Frage – {bild_id}: {beschreibung}]')

    if frage['typ'] == 'freitext':
        muster = (frage.get('musterantwort') or {}).get('text', '')
        print(f'MUSTERANTWORT (amtlich): {muster}')
    else:
        for option in frage.get('optionen') or []:
            marke_o = 'RICHTIG' if option['korrekt'] else 'falsch '
            print(f'  [{marke_o}] {option["label"]}) {option["inhalt"]["text"]}')
            for bild_id in option.get('bilder') or []:
                beschreibung = bilder.get(bild_id, '(kein Alternativtext hinterlegt)')
                print(f'      [Abbildung – {bild_id}: {beschreibung}]')
    print()


def main() -> int:
    zerleger = argparse.ArgumentParser(description='Fragen des amtlichen Katalogs ausgeben.')
    zerleger.add_argument('--kapitel')
    zerleger.add_argument('--abschnitt')
    zerleger.add_argument('--id', nargs='+')
    zerleger.add_argument('--von', type=int, default=0)
    zerleger.add_argument('--anzahl', type=int)
    zerleger.add_argument('--nur-ids', action='store_true')
    zerleger.add_argument('--ohne-erklaerung', action='store_true',
                          help='Nur Fragen, zu denen noch keine Erklärung vorliegt')
    zerleger.add_argument('--wortform', nargs='+',
                          help='Zählt Vorkommen dieser Wortformen im Katalog (für das Glossar)')
    argumente = zerleger.parse_args()

    katalog = json.load(io.open(KATALOG, encoding='utf-8'))

    if argumente.wortform:
        return wortform_suchen(argumente.wortform, katalog)
    schon_da = vorhandene_erklaerungen()
    bilder = alttexte()

    fragen = katalog['fragen']
    if argumente.id:
        gesucht = set(argumente.id)
        fragen = [f for f in fragen if f['id'] in gesucht]
    else:
        if argumente.kapitel:
            fragen = [f for f in fragen if f['kapitel'] == argumente.kapitel]
        if argumente.abschnitt:
            fragen = [f for f in fragen if f.get('abschnitt') == argumente.abschnitt]

    if argumente.ohne_erklaerung:
        fragen = [f for f in fragen if f['id'] not in schon_da]

    fragen = fragen[argumente.von :]
    if argumente.anzahl is not None:
        fragen = fragen[: argumente.anzahl]

    if argumente.nur_ids:
        for frage in fragen:
            print(frage['id'])
        return 0

    print(f'# {len(fragen)} Fragen\n')
    for frage in fragen:
        frage_zeigen(frage, bilder, schon_da)
    return 0


if __name__ == '__main__':
    sys.stdout.reconfigure(encoding='utf-8', errors='replace')  # type: ignore[union-attr]
    raise SystemExit(main())
