#!/usr/bin/env python3
"""check_abbreviations.py — Prüft Artikel-HTML auf undefinierte Abkürzungen.
Exit 0 = alle definiert, Exit 1 = undefinierte gefunden, Exit 2 = Fehler.
Usage: python3 check_abbreviations.py <artikel.html>
"""
import re, sys
from html import unescape
from collections import Counter

SKIP_WORDS = {
    'UND','ODER','DIE','DER','DAS','IST','VON','MIT','AUF','FÜR','DEN','DEM',
    'DES','EIN','EINE','EINEN','EINER','NACH','ZUM','ZUR','BEI','IM','AM','UM',
    'BIS','ZWEI','DREI','VIER','FÜNF','DANN','NOCH','AUCH','NUN','WIE','WAS',
    'WER','WAR','HAT','HABEN','NICHT','KEIN','ALLE','ALLEN','ALLER','OHNE',
    'ABER','ODER','SEIT','SCHON','MEHR','NUR','ALS','WIRD','WURDE','WORDEN',
    'SIND','SEIN','HIER','DA','DORT','JETZT','HEUTE','MORGEN','GESTERN',
    'IHR','IHRE','IHREN','IHM','IHN','IHRER','SIE','WIR','UNS','EUCH','EUER',
    'MIR','MICH','MEIN','MEINE','DEIN','DEINE','SEINE','SEINER',
    'SPIEL','SPIELE','JAHR','JAHRE','JULI','JUNI','MAI','MÄRZ','APRIL',
    'JANUAR','FEBRUAR','AUGUST','SEPTEMBER','OKTOBER','NOVEMBER','DEZEMBER',
    'EURO','USD','MIN','MAX','CA','ET','AL','US','UK','DE','EN','FR','ES',
    'IT','JP','CN','QR','AI','KI','KW','NR','KG','MB','GB','CM','MM','KM',
    'ML','DL','LLC','AG','GMBH','CO','KGAA','ORF','CD','DVD','PC','TV','VHS',
    'DIY','CEO','CFO','COO','CTO','PR','HR','IT','OG','OGS',
    'SPD','CDU','CSU','FDP','AFD','BSW','ARD','ZDF','RTL','WDR','NDR','SWR',
    'BR','MDR','HRR','RBB','DLF','DPA','AFP','AP','CNN','BBC','NYT','WSJ',
    'SZ','FAZ','SPON','TAG','SON','MONTAG','DIENSTAG','MITTWOCH','DONNERSTAG',
    'FREITAG','SAMSTAG','SONNTAG','AKTION','SALE','BESTE','MONOLOGO','SILOS',
    'UP','BSK','CATAN','PM','SU&SD','SUSD',
    'SET','BOX','BIG','NEW','OLD','TOP','PRO','PLUS','ONE','TWO',
    'USA','USD','DC',  # Common knowledge / proper names
}

SKIP_PATTERNS = [
    r'^[A-Z]+\d',          # Product codes like S1R
    r'^[A-Z]{2,}_',        # Snake-case IDs
]

PROPER_NAME_NEXT = re.compile(
    r'\b(Advisory|Games|Verlag|Group|Entertainment|Productions?|Studios?|Media|'
    r'Press|Publishing|International|Worldwide|Digital|Software|Interactive|'
    r'Awards?|Supplement|Edition|Expansion|League|Legends|Party|Quest)\b'
)

# Known expansion terms for common abbreviations
KNOWN_EXPANSIONS = {
    'BGG': ['BoardGameGeek', 'Board Game Geek'],
    'TCG': ['Trading Card Game', 'Trading Card Games'],
    'CCG': ['Collectible Card Game', 'Collectible Card Games', 'UniVersus', 'Guilty Gear'],
    'MTG': ['Magic The Gathering', 'Magic: The Gathering'],
    'RPG': ['Roleplaying Game', 'Role Playing Game', 'Rollenspiel'],
    'TTRPG': ['Tabletop Roleplaying', 'Tabletop Rollenspiel', 'Tabletop-Rollenspiel'],
    'VFX': ['Visual Effects', 'Spezialeffekte'],
    'UVP': ['unverbindliche Preisempfehlung', 'unverbindlichen Preisempfehlung'],
    'SHUX': ['Community-Treffen', 'Convention', 'jährliche'],
    'GAMA': ['Tabletop Game Association'],
    'KTBG': ['Kids Table Board Gaming'],
    'CMYK': ['CMYK Games'],
    'SVS': ['Schweizer Spielwarenverband'],
}


def has_expansion_nearby(ctx, abbr):
    """Check if known expansion for this abbreviation appears nearby."""
    if abbr not in KNOWN_EXPANSIONS:
        return False
    for term in KNOWN_EXPANSIONS[abbr]:
        if re.search(re.escape(term), ctx, re.IGNORECASE):
            return True
    return False


def is_in_parens_definition(ctx, abbr):
    """Check for any definition-in-parentheses pattern around the abbreviation."""
    # Strip the base abbr (no plural 's')
    base = abbr.rstrip('s') if abbr.endswith('s') else abbr
    
    # Pattern A: "Full Term (ABBR)" — definition BEFORE
    # e.g., "Tabletop-Rollenspiele (TTRPGs)", "Kids Table Board Gaming (KTBG)"
    a = re.search(
        r'([A-ZÄÖÜ][A-Za-zäöüß\-]{3,}(?:\s[A-ZÄÖÜ][A-Za-zäöüß\-]{2,}){0,6})'
        r'\s*\(' + re.escape(base) + r's?\)', ctx)
    if a:
        return True
    
    # Pattern B: "ABBR (explanation)" or "ABBR-Compound (explanation)" — definition AFTER
    # e.g., "VFX-Künstler (Visual Effects)", "SHUX-Convention (das jährliche Treffen)"
    b = re.search(
        re.escape(base) + r's?(?:-[A-Za-zäöüß]+)?\s*\(([^)]{3,80})\)', ctx)
    if b:
        return True
    
    # Pattern C: "FullTerm(ABBR)" — no space
    # e.g., "BoardGameGeek(BGG)"
    c = re.search(
        r'([A-ZÄÖÜ][A-Za-zäöüß\-]{3,}(?:\s[A-ZÄÖÜ][A-Za-zäöüß\-]{2,}){0,4})'
        r'\(' + re.escape(base) + r's?\)', ctx)
    if c:
        return True
    
    return False


def is_full_term_before(ctx, abbr):
    """Check if full term appears right before abbreviation without parens.
    e.g., "Schweizer Spielwarenverband SVS"
    """
    base = abbr.rstrip('s') if abbr.endswith('s') else abbr
    m = re.search(
        r'([A-ZÄÖÜ][A-Za-zäöüß\-]{3,}(?:\s[A-ZÄÖÜ][A-Za-zäöüß\-]{2,}){1,6})'
        r'\s+' + re.escape(base) + r's?(?:\s|[,.!?;:\)]|$)', ctx)
    if m:
        full_term = m.group(1)
        if len(full_term) > 10:
            return True
    return False


def main():
    if len(sys.argv) < 2:
        print("Usage: check_abbreviations.py <artikel.html>", file=sys.stderr)
        sys.exit(2)
    
    filepath = sys.argv[1]
    
    try:
        with open(filepath) as f:
            content = f.read()
    except FileNotFoundError:
        print(f"File not found: {filepath}", file=sys.stderr)
        sys.exit(2)
    
    # Extract article body
    m = re.search(r'<article>(.*?)</article>', content, re.DOTALL)
    txt = m.group(1) if m else content
    txt = re.sub(r'<[^>]+>', ' ', txt)
    txt = re.sub(r'\s+', ' ', txt).strip()
    txt = unescape(txt)
    
    # Find Quellen section boundary (less strict checks there)
    # There may be multiple Quellen blocks (old + new format).
    # Find the FIRST one and skip everything after it (with some margin)
    quellen_pos = txt.find('Quellen')
    if quellen_pos < 0:
        quellen_pos = len(txt)
    else:
        # Allow 250 chars before the "Quellen" heading for old-style source blocks
        quellen_pos = max(0, quellen_pos - 250)
    
    # Find all ALL-CAPS sequences (2+ letters)
    abbrs = re.findall(r'\b[A-Z]{2,}(?:&[A-Z]{2,})?\b', txt)
    counts = Counter(abbrs)
    
    # Filter candidates
    candidates = {}
    for abbr, count in counts.items():
        if abbr in SKIP_WORDS:
            continue
        if any(re.match(p, abbr) for p in SKIP_PATTERNS):
            continue
        candidates[abbr] = count
    
    issues = []
    for abbr in sorted(candidates.keys()):
        # Match both singular and plural: ABBR or ABBRs
        pattern = re.compile(r'\b' + re.escape(abbr) + r's?\b')
        first = pattern.search(txt)
        if not first:
            continue
        
        pos = first.start()
        
        # Skip if in Quellen section
        if 0 < quellen_pos < pos:
            continue
        
        # Context after the match
        ctx_after = txt[pos:pos+80]
        
        # Skip proper names (followed by Advisory, Games, Verlag, Awards, etc.)
        if PROPER_NAME_NEXT.search(ctx_after[:50]):
            continue
        
        # Skip slash-separated lists
        before_slash = txt[max(0, pos-15):pos]
        after_slash = txt[pos+len(first.group()):pos+len(first.group())+15]
        if '/' in before_slash or '/' in after_slash:
            continue
        
        # Build full context for definition checking
        ctx_start = max(0, pos - 250)
        ctx_end = min(len(txt), pos + 150)
        ctx = txt[ctx_start:ctx_end]
        
        # Check definition patterns
        if is_in_parens_definition(ctx, abbr):
            continue
        if is_full_term_before(ctx, abbr):
            continue
        if has_expansion_nearby(ctx, abbr):
            continue
        
        # Build issue report
        ctx_before = txt[max(0, pos-40):pos]
        issues.append(
            f'{abbr}: ...{ctx_before[-60:]}«{first.group()}»{ctx_after[:60]}...')
    
    if issues:
        for i in issues:
            print(f'  UNDEFINIERT: {i}')
        sys.exit(1)
    else:
        print('  Alle Abkürzungen definiert')
        sys.exit(0)

if __name__ == '__main__':
    main()
