"""
OCR-tolerant fuzzy regex builder for Turkish text.

Strategy: 2014-vintage Turkish OCR systematically destroys diacritics.
Each character is replaced with a character class that covers all
plausible OCR misreadings. See: keywords/kirim.yaml notes column.
"""
import re
import unicodedata

# Character → tolerant character class mapping.
# Order matters in DIACRITIC_CLASSES: lookup is case-folded.
DIACRITIC_CLASSES = {
    # The ı/i/I/İ family — the most damaged
    'i': r'[1iIıİlj|!]',
    'ı': r'[1iIıİlj|!]',
    # Sibilants
    's': r'[s$ş]',
    'ş': r'[s$ş~]',
    # Plosives
    'c': r'[cç]',
    'ç': r'[cç]',
    'g': r'[gğ]',
    'ğ': r'[gğq]',
    # Vowels
    'u': r'(?:[uü]|ii)',
    'ü': r'(?:[uü]|ii)',
    'o': r'[oö0]',
    'ö': r'[oö0]',
    'a': r'[aâå]',
    'â': r'[aâå]',
    'e': r'[eé]',
}

# Non-letter separators in OCR can be space, dash, underscore, tilde, dot.
WORD_SEP = r'[\s\-_~.,]+'


def turkish_lower(s: str) -> str:
    """Turkish-aware lowercase: İ→i, I→ı."""
    return s.replace('İ', 'i').replace('I', 'ı').lower()


def build_pattern(word: str) -> str:
    """Build OCR-tolerant regex for a single word or phrase."""
    parts = []
    for ch in word:
        lower = turkish_lower(ch)
        if lower in DIACRITIC_CLASSES:
            parts.append(DIACRITIC_CLASSES[lower])
        elif ch == ' ':
            parts.append(WORD_SEP)
        elif ch.isalpha():
            # Plain ASCII letter — case-insensitive
            parts.append(f'[{ch.lower()}{ch.upper()}]')
        else:
            parts.append(re.escape(ch))
    # Word boundaries: \b doesn't work well with character classes,
    # so use lookarounds for non-letter context.
    return r'(?<![\wıİşŞçÇğĞüÜöÖâÂ])' + ''.join(parts) + r'(?![\wıİşŞçÇğĞüÜöÖâÂ])'


def build_pattern_with_suffixes(word: str, suffixes: list = None) -> str:
    """Build pattern allowing optional Turkish suffixes."""
    base = build_pattern(word)
    # Strip trailing boundary, add suffix group, re-add boundary
    base_no_end = base[:-len(r'(?![\wıİşŞçÇğĞüÜöÖâÂ])')]
    if suffixes:
        suffix_alts = '|'.join(re.escape(s) for s in suffixes)
        suffix_group = rf'(?:{suffix_alts})?'
        return base_no_end + suffix_group + r'(?![\wıİşŞçÇğĞüÜöÖâÂ])'
    return base


def compile_keyword_set(keyword_data: dict) -> list:
    """
    Compile a YAML keyword set into a list of (label, weight, regex) tuples.
    Higher-weight matches surface first in reports.
    """
    compiled = []
    # Main keywords
    for kw in keyword_data.get('keywords', []):
        canonical = kw['canonical']
        aliases = kw.get('aliases', [])
        suffixes = kw.get('suffixes', [])
        weight = kw.get('weight', 3)
        for term in [canonical] + aliases:
            try:
                pat = build_pattern_with_suffixes(term, suffixes)
                compiled.append((canonical, weight, re.compile(pat, re.IGNORECASE | re.UNICODE)))
            except re.error as e:
                print(f"  [warn] regex compile failed for {term!r}: {e}")
    # Proper nouns (smoking guns)
    for pn in keyword_data.get('proper_nouns', []):
        canonical = pn['canonical']
        aliases = pn.get('aliases', [])
        weight = pn.get('weight', 5)
        for term in [canonical] + aliases:
            try:
                pat = build_pattern(term)
                compiled.append((canonical, weight, re.compile(pat, re.IGNORECASE | re.UNICODE)))
            except re.error as e:
                print(f"  [warn] regex compile failed for {term!r}: {e}")
    return compiled


def compile_disambiguators(keyword_data: dict) -> list:
    """Compile false-positive filter patterns."""
    return [
        re.compile(build_pattern(term), re.IGNORECASE | re.UNICODE)
        for term in keyword_data.get('disambiguators', [])
    ]


def is_false_positive(text: str, match_start: int, match_end: int,
                      disambiguators: list, window: int = 50) -> bool:
    """Check if match falls inside a disambiguator (e.g., 'Kerim Bey' near 'Kırım')."""
    win_start = max(0, match_start - window)
    win_end = min(len(text), match_end + window)
    window_text = text[win_start:win_end]
    for dis_re in disambiguators:
        if dis_re.search(window_text):
            return True
    return False


def extract_snippet(text: str, match_start: int, match_end: int,
                    radius: int = 200) -> str:
    """Extract a clean ±radius snippet around a match."""
    s = max(0, match_start - radius)
    e = min(len(text), match_end + radius)
    snip = text[s:e]
    # Collapse whitespace, drop weird control chars
    snip = re.sub(r'\s+', ' ', snip).strip()
    snip = ''.join(c for c in snip if c.isprintable() or c in ' \n')
    return snip


def co_occurrence_score(text: str, term_a: str, term_b: str,
                        compiled_patterns: dict, window: int = 300) -> int:
    """
    Count how many times term_a and term_b appear within `window` chars of each other.
    Used by report renderer for boost scoring.
    """
    if term_a not in compiled_patterns or term_b not in compiled_patterns:
        return 0
    a_positions = [m.start() for m in compiled_patterns[term_a].finditer(text)]
    b_positions = [m.start() for m in compiled_patterns[term_b].finditer(text)]
    score = 0
    for ap in a_positions:
        for bp in b_positions:
            if abs(ap - bp) <= window:
                score += 1
    return score


if __name__ == '__main__':
    # Smoke test
    test_words = ['Kırım', 'Bahçesaray', 'Cafer Seydahmet', 'İsmail Gaspıralı']
    test_text = """
    OCR çöplüğü:
    K1r1m Tatarlari hakkinda bir haber.
    Bahcesaray'da bir hadise.
    K~r~m Hanl1g1 tarihi.
    Cafer Seydamet Bey istanbula geldi.
    Ismail Gaspirali'nin 1934 anma toplantisi.
    Kerim Bey ile karistirma — bu yanlis pozitif.
    """
    for w in test_words:
        pat = build_pattern(w)
        print(f"\n{w!r} → {pat}")
        for m in re.finditer(pat, test_text, re.IGNORECASE):
            print(f"  hit: {m.group(0)!r} @ {m.start()}")
