#!/usr/bin/env python3
"""Decide TODOS los pares de una palabra que siguen abiertos, con evidencia.

Cada candidato acaba en uno de estos estados, y ninguno se queda sin motivo:

  RUIDO      la lectura del impreso no existe en ninguna otra parte del libro,
             o las tres pasadas de OCR no coinciden -> reconocimiento no fiable.
  APLICADO   la forma del repo no aparece NUNCA en el escaneo, la del impreso si,
             las tres lecturas coinciden y pasa los guardarrailes (diccionario,
             frecuencia, nada de sanscrito ni de lineas de sinonimos).
  IMAGEN     el impreso usa las dos formas o la lectura aparece una sola vez:
             no se puede decidir sin ver la pagina. Queda anotado como tal.

Uso: python3 decidir_pares.py [--aplicar]
"""
import json, re, sys, collections, pathlib, unicodedata

HERE = pathlib.Path(__file__).resolve().parent
REPO = pathlib.Path('/Users/jaganat/git_projects/vedabase-original')
TOK = re.compile(r"[a-z']+")
DIC = set()
_d = pathlib.Path('/usr/share/dict/words')
if _d.exists():
    DIC = {w.strip().lower() for w in _d.read_text(errors='ignore').splitlines() if w.strip()}
PERMITIDAS = {'benefitted', 'travelled', 'alloted', 'enamoured', 'advertize', 'despatched',
              'criticise', 'worshipable', 'neighbourhood', 'wilfully', 'fourty', 'behaviour'}


def sd(s):
    return ''.join(c for c in unicodedata.normalize('NFD', s or '')
                   if unicodedata.category(c) != 'Mn')


def en_dic(w):
    if w in DIC: return True
    for suf, base in (('s', ''), ('es', ''), ('ed', ''), ('ed', 'e'), ('ing', ''),
                      ('ing', 'e'), ('ies', 'y'), ('d', '')):
        if w.endswith(suf) and (w[:-len(suf)] + base) in DIC: return True
    return False


_idx = {}
def indice(lib):
    if lib not in _idx:
        d = HERE / 'surya' / lib
        subs = [x for x in d.iterdir() if x.is_dir()] if d.exists() else []
        c = collections.Counter()
        for sub in subs:
            for p in sub.glob('*.surya.txt'):
                c.update(TOK.findall(sd(p.read_text(encoding='utf-8', errors='ignore')).lower()))
        _idx[lib] = c
    return _idx[lib]


def main():
    aplicar = '--aplicar' in sys.argv
    abiertos = {(r['file'], r['idx']): r for r in map(json.loads, open(HERE / 'ledger.jsonl', encoding='utf-8'))
                if r['estado'] == 'ABIERTO'}
    # lecturas de OCR y recuento por (fichero, par)
    lecturas = collections.defaultdict(set); casos = collections.defaultdict(list)
    paginas_de = {}
    for cf in (HERE / 'out_surya').glob('*.candidates.jsonl'):
        libro = cf.name.replace('.candidates.jsonl', '')
        for i, l in enumerate(open(cf, encoding='utf-8')):
            d = json.loads(l)
            if (d['file'], i) not in abiertos: continue
            a = sd(d.get('repo') or '').strip().lower(); b = sd(d.get('scan') or '').strip().lower()
            if not (a and b and ' ' not in a and ' ' not in b): continue
            k = (libro, d['file'], a, b)
            casos[k].append(i)
            pg = (d.get('page') or '').split('#')[-1]
            if pg: paginas_de.setdefault(k, pg)
            for c in ('scan', 'scan6', 'scan4'):
                if d.get(c): lecturas[k].add(sd(d[c]).strip().lower())

    _pag = {}
    def texto_pagina(libro, pg):
        k = (libro, pg)
        if k not in _pag:
            d = HERE / 'surya' / libro
            subs = [x for x in d.iterdir() if x.is_dir()] if d.exists() else []
            txt = ''
            for sub in subs:
                f = sub / f'{pg}.surya.txt'
                if f.exists(): txt = sd(f.read_text(encoding='utf-8', errors='ignore')); break
            _pag[k] = txt
        return _pag[k]

    res = collections.Counter(); verdicts = []; aplicables = collections.defaultdict(set)
    for (libro, fich, a, b), idxs in casos.items():
        w = indice(libro)
        if not w: res['sin OCR'] += len(idxs); continue
        na, nb = w.get(a, 0), w.get(b, 0)
        if nb == 0:
            res['RUIDO: la lectura del impreso no existe en el libro'] += len(idxs)
            verdicts.append((a, b, 'DESCARTADO', 'la lectura del impreso no aparece en ninguna otra parte del libro: reconocimiento no fiable')); continue
        if len(lecturas[(libro, fich, a, b)]) > 1:
            res['RUIDO: las tres pasadas de OCR no coinciden'] += len(idxs)
            verdicts.append((a, b, 'DESCARTADO', 'las tres pasadas de OCR leen cosas distintas: no hay lectura fiable')); continue
        if na:
            # El libro usa las dos formas, pero la PAGINA concreta suele ser
            # inequivoca: si en ella aparece la del impreso y no la del repo,
            # decide la pagina. Validado por imagen en el TLC (Paramatman, p.37).
            pg = paginas_de.get((libro, fich, a, b))
            decide = None
            if pg:
                tp = texto_pagina(libro, pg)
                if tp:
                    hay_a = re.search(rf'\b{re.escape(a)}\b', tp, re.I)
                    hay_b = re.search(rf'\b{re.escape(b)}\b', tp, re.I)
                    if hay_b and not hay_a: decide = True
                    elif hay_a: decide = False
            if decide is False:
                res['RUIDO: la pagina trae la forma del repo'] += len(idxs)
                verdicts.append((a, b, 'DESCARTADO', 'la pagina del impreso trae la forma del repo: el candidato es del alineador')); continue
            if decide is not True:
                res['IMAGEN: el impreso usa las dos formas'] += len(idxs)
                verdicts.append((a, b, 'ABIERTO', f'el impreso usa AMBAS formas ({na} vs {nb}): requiere ver la pagina')); continue
            # decidido por la pagina: sigue a los guardarrailes de abajo
        if nb == 1:
            res['IMAGEN: la lectura del impreso aparece una sola vez'] += len(idxs)
            verdicts.append((a, b, 'ABIERTO', 'la lectura del impreso aparece una sola vez: requiere ver la pagina')); continue
        if re.search(r'[āīūṛṅñṭḍṇśṣḥṁ]', a) or re.search(r'[āīūṛṅñṭḍṇśṣḥṁ]', b):
            res['IMAGEN: transliteracion con diacriticos'] += len(idxs)
            verdicts.append((a, b, 'ABIERTO', 'transliteracion sanscrita: el OCR confunde y/v y b/v, requiere imagen')); continue
        if b not in PERMITIDAS and (not en_dic(b) or en_dic(a)):
            res['IMAGEN: guardarrail de diccionario'] += len(idxs)
            verdicts.append((a, b, 'ABIERTO', 'ambas son palabras inglesas, o la del impreso no lo es: requiere imagen')); continue
        res['APLICABLE'] += len(idxs)
        aplicables[fich].add((a, b))

    for k, v in res.most_common(): print(f'  {v:5}  {k}')
    print('\npares aplicables:', sum(len(v) for v in aplicables.values()), 'en', len(aplicables), 'ficheros')
    if aplicar:
        n = 0
        for fich, pares in aplicables.items():
            p = REPO / fich
            if not p.exists(): continue
            t = orig = p.read_text(encoding='utf-8')
            for a, b in pares:
                def f(m): return b.capitalize() if m.group(0)[:1].isupper() else b
                t, c = re.subn(rf'\b{re.escape(a)}\b', f, t, flags=re.I); n += c
            if t != orig: p.write_text(t, encoding='utf-8')
        print('sustituciones en el repo:', n)
        json.dump({k: sorted(v) for k, v in aplicables.items()},
                  open(HERE / 'ultimas_grafias_masivo.json', 'w'), ensure_ascii=False, indent=1)
        with open(HERE / 'verdicts.jsonl', 'a') as fh:
            for a, b, est, raz in verdicts:
                fh.write(json.dumps({"repo": a, "scan": b, "estado": est, "razon": raz,
                                     "fecha": "2026-08-09"}, ensure_ascii=False) + '\n')
            for fich, pares in aplicables.items():
                for a, b in pares:
                    fh.write(json.dumps({"repo": a, "scan": b, "estado": "APLICADO",
                                         "razon": "la forma del repo no aparece en el escaneo, la del impreso si, con consenso de OCR y guardarrailes",
                                         "fecha": "2026-08-09"}, ensure_ascii=False) + '\n')
        print('veredictos escritos:', len(verdicts) + sum(len(v) for v in aplicables.values()))


if __name__ == '__main__':
    main()
