#!/usr/bin/env python3
"""Adjudica los candidatos abiertos con una SEGUNDA OPINION de OCR.

Hallazgo (2026-08-09): los PDF de `scan_vedabase` llevan capa de texto embebida,
producida por un motor de OCR DISTINTO de Surya. Es decir: para cada pagina hay
dos lecturas independientes. Cuando ambas coinciden, la lectura esta decidida
sin necesidad de mirar la imagen; cuando discrepan, la imagen sigue siendo
obligatoria.

Comprobado antes de usarlo, contra paginas que yo mismo habia leido a ojo:
  - p0125: la capa dice 'Hiranygarbha' y Surya leia 'hiranyagarbha'. La imagen
    da la razon a la capa. O sea que sirve incluso donde Surya falla.
  - p0125 'Garbhodakshayee' x5, p0082 'exchange of loving': coinciden.

Decision por candidato:
  APLICADO    la capa trae la lectura del impreso y NO la del repo -> confirmado
              por dos motores.
  DESCARTADO  la capa trae la lectura del REPO -> el candidato era del alineador.
  ABIERTO     la capa no contiene ninguna de las dos -> requiere imagen.

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

HERE = pathlib.Path(__file__).resolve().parent
REPO = pathlib.Path('/Users/jaganat/git_projects/vedabase-original')
SCAN = pathlib.Path('/Users/jaganat/git_projects/scan_vedabase')
CACHE = HERE / 'capa_texto'
CACHE.mkdir(exist_ok=True)


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


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',
              'benedicted', 'codes'}


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


def pdfs_de(libro):
    b = json.load(open(HERE / 'books.json'))['books']
    return [SCAN / p for p in b.get(libro, [])]


def capa(libro):
    """Texto de la capa del PDF, pagina a pagina. Se cachea en disco: extraerlo
    entero tarda segundos, hacerlo por pagina tardaria horas."""
    f = CACHE / f'{libro}.json'
    if f.exists():
        return json.load(open(f))
    paginas = {}
    for pdf in pdfs_de(libro):
        if not pdf.exists():
            continue
        try:
            out = subprocess.run(['pdftotext', str(pdf), '-'], capture_output=True,
                                 text=True, timeout=1800).stdout
        except Exception:
            continue
        # el separador de pagina de pdftotext es el form feed
        for i, txt in enumerate(out.split('\f'), start=1):
            paginas.setdefault(f'{pdf.stem}#p{i:04d}', txt)
    json.dump(paginas, open(f, 'w'), ensure_ascii=False)
    return paginas


def main():
    libros = [a for a in sys.argv[1:] if not a.startswith('--')]
    aplicar = '--aplicar' in sys.argv
    abiertos = {(r['file'], r['idx']) for r in map(json.loads, open(HERE / 'ledger.jsonl', encoding='utf-8'))
                if r['estado'] == 'ABIERTO'}
    res = collections.Counter(); verd = []; aplicables = collections.defaultdict(set)
    casos_por = collections.Counter()

    for cf in sorted((HERE / 'out_surya').glob('*.candidates.jsonl')):
        libro = cf.name.replace('.candidates.jsonl', '')
        if libros and libro not in libros:
            continue
        pags = capa(libro)
        if not pags:
            res[f'{libro}: sin capa de texto'] += 1
            continue
        for i, l in enumerate(open(cf, encoding='utf-8')):
            if (json.loads(l)['file'], i) not in abiertos:
                continue
            d = json.loads(l)
            a = sd(d.get('repo') or '').strip(); b = sd(d.get('scan') or '').strip()
            if not (a and b) or len(a) < 3 or len(b) < 3:
                continue
            pg = d.get('page') or ''
            txt = pags.get(pg)
            if txt is None:                       # el nombre del PDF puede variar
                clave = next((k for k in pags if k.endswith(pg.split('#')[-1])), None)
                txt = pags.get(clave) if clave else None
            if not txt:
                res['sin esa pagina en la capa'] += 1; continue
            t = sd(txt)
            hay_a = re.search(rf'\b{re.escape(a)}\b', t, re.I)
            hay_b = re.search(rf'\b{re.escape(b)}\b', t, re.I)
            if hay_b and not hay_a:
                al, bl = a.lower(), b.lower()
                # GUARDARRAILES. La capa confirma lo que dice ESA pagina, pero la
                # sustitucion se hace en todo el fichero: si son dos palabras
                # inglesas corrientes ('words'->'word', 'what'->'not') el cambio
                # arrasa el capitulo. Paso el 2026-08-09 y hubo que revertir.
                if bl not in PERMITIDAS and (en_dic(al) and en_dic(bl)):
                    res['APARTADO: ambas son palabras inglesas'] += 1
                    verd.append((al, bl, 'ABIERTO',
                                 'la capa confirma esa pagina, pero ambas son palabras inglesas corrientes: requiere imagen y sustitucion posicional'))
                    continue
                if re.search(r'[āīūṛṅñṭḍṇśṣḥṁ]', a) or re.search(r'[āīūṛṅñṭḍṇśṣḥṁ]', b):
                    res['APARTADO: transliteracion con diacriticos'] += 1
                    verd.append((al, bl, 'ABIERTO', 'transliteracion sanscrita: requiere imagen')); continue
                linea = next((ln for ln in (d.get('repo_ctx') or '').splitlines()
                              if re.search(rf'\b{re.escape(al)}\b', ln, re.I)), '')
                if linea.count('—') > 3 and linea.count('*') > 6:
                    res['APARTADO: linea de sinonimos (sanscrito)'] += 1
                    verd.append((al, bl, 'ABIERTO', 'glosa palabra-por-palabra: requiere imagen')); continue
                res['CONFIRMADO por los dos motores'] += 1
                aplicables[d['file']].add((a.lower(), b.lower()))
                casos_por[(d['file'], a.lower(), b.lower())] += 1
            elif hay_a and not hay_b:
                res['DESCARTADO: la capa trae la forma del repo'] += 1
                verd.append((a.lower(), b.lower(), 'DESCARTADO',
                             'la capa de texto del PDF trae la forma del repo: Surya se equivocaba'))
            elif hay_a and hay_b:
                res['la pagina trae las dos formas: requiere imagen'] += 1
            else:
                res['ninguna de las dos en la capa: requiere imagen'] += 1

    for k, v in res.most_common(): print(f'  {v:6}  {k}')
    print('\npares confirmados:', sum(len(v) for v in aplicables.values()),
          'en', len(aplicables), 'ficheros')
    if aplicar:
        n = 0; saltadas = 0
        for fich, pares in list(aplicables.items()):
            p = REPO / fich
            if not p.exists(): continue
            t = orig = p.read_text(encoding='utf-8')
            buenos = set()
            for a, b in sorted(pares):
                # GARANTIA DE RECUENTO: solo se sustituye si el fichero tiene
                # tantas ocurrencias como casos senalo el alineador. Si hay mas,
                # la evidencia de una pagina no basta para todas.
                hall = re.findall(rf'\b{re.escape(a)}\b', t, flags=re.I)
                if len(hall) != casos_por.get((fich, a, b), 0):
                    saltadas += 1
                    verd.append((a, b, 'ABIERTO',
                                 f'la capa confirma esa pagina, pero el fichero trae {len(hall)} ocurrencias '
                                 f'y solo {casos_por.get((fich, a, b), 0)} estan senaladas: requiere sustitucion posicional'))
                    continue
                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
                buenos.add((a, b))
                verd.append((a, b, 'APLICADO',
                             'la capa de texto del PDF (otro motor de OCR) confirma la lectura de Surya, '
                             'y el recuento del fichero cuadra con los casos senalados'))
            aplicables[fich] = buenos
            if t != orig: p.write_text(t, encoding='utf-8')
        print('apartadas por no cuadrar el recuento:', saltadas)
        print('sustituciones en el repo:', n)
        json.dump({k: sorted(v) for k, v in aplicables.items()},
                  open(HERE / 'ultimas_grafias_segunda.json', 'w'), ensure_ascii=False, indent=1)
        with open(HERE / 'verdicts.jsonl', 'a') as fh:
            for a, b, est, raz in verd:
                fh.write(json.dumps({"repo": a, "scan": b, "estado": est, "razon": raz,
                                     "fecha": "2026-08-09"}, ensure_ascii=False) + '\n')
        print('veredictos escritos:', len(verd))


if __name__ == '__main__':
    main()
