#!/usr/bin/env python3
"""Aplica al repo los veredictos de la adjudicacion POR IMAGEN.

Distinto de `deshacer_imagen.py`: aquel revisaba cambios YA aplicados, este
adjudica candidatos que seguian ABIERTOS. Aqui `IMPRESO=SCAN` significa
"aplicar", no "conservar".

  IMPRESO=SCAN   se aplica, y SOLO en la ocurrencia que senala el contexto del
                 alineador (mismo metodo que aplicar_posicional.py). Queda
                 VERIFICADO con la cita de pagina: no vuelve a proponerse.
  IMPRESO=REPO   el impreso da la razon al repo -> DESCARTADO, no se toca nada.
  IMPRESO=OTRO   el impreso trae una tercera lectura. NO se aplica en automatico:
                 el campo `real` suele ser una frase entera y a veces revela un
                 hueco de texto, no una grafia. Se deja ABIERTO con la lectura
                 anotada para resolverlo a mano.
  NO_LEGIBLE     el impreso no se distingue (hueco de tinta). DESCARTADO por
  NO_ENCONTRADO  falta de prueba: sin ver el pixel no se cambia el texto.

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

HERE = pathlib.Path(__file__).resolve().parent
REPO = pathlib.Path('/Users/jaganat/git_projects/vedabase-original')
LOTES = HERE / 'lotes_imagen'
FECHA = '2026-08-09'
sys.path.insert(0, str(HERE))
from aplicar_posicional import normaliza


def dia(s):
    return sum(1 for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) == 'Mn')


def main():
    aplicar = '--aplicar' in sys.argv
    desde = int(sys.argv[sys.argv.index('--desde') + 1]) if '--desde' in sys.argv else 10

    filas = []
    for f in sorted(LOTES.glob('resultado*.json')):
        n = int(re.search(r'(\d+)', f.name).group(1))
        if n < desde: continue
        filas += json.load(open(f, encoding='utf-8'))
    if not filas:
        print('sin resultados'); return 1
    # DEDUPLICAR. El mismo candidato puede llegar por dos lotes, y el aplicador
    # los escribia los dos sobre la MISMA posicion: 'toward' -> 'towards' dos
    # veces dejo 'towardss', y 'upward' tres veces dejo 'upwardsss' (2026-08-09).
    vistos = set(); unicas = []
    for x in filas:
        k = (x['file'], x['repo'].lower(), x['scan'].lower(), x['pagina'])
        if k in vistos: continue
        vistos.add(k); unicas.append(x)
    if len(unicas) != len(filas):
        print(f'  filas duplicadas descartadas: {len(filas) - len(unicas)}')
    filas = unicas
    res = collections.Counter(x['veredicto'] for x in filas)
    for k, v in res.most_common(): print(f'  {v:5}  {k}')

    # contexto de cada candidato, para la sustitucion posicional
    quiero = {(x['file'], x['repo'].lower(), x['scan'].lower())
              for x in filas if x['veredicto'] == 'IMPRESO=SCAN'}
    ctxs = collections.defaultdict(list)
    for cf in sorted((HERE / 'out_surya').glob('*.candidates.jsonl')):
        for l in open(cf, encoding='utf-8'):
            d = json.loads(l)
            k = (d['file'], (d.get('repo') or '').strip().lower(),
                 (d.get('scan') or '').strip().lower())
            if k in quiero and d.get('context'):
                ctxs[k].append(d['context'])

    hechas = []; diacr = []; sinpos = []; fall = collections.Counter()
    porfich = collections.defaultdict(list)
    for x in filas:
        if x['veredicto'] == 'IMPRESO=SCAN':
            porfich[x['file']].append(x)

    for fich, casos in sorted(porfich.items()):
        p = REPO / fich
        if not p.exists(): fall['sin fichero'] += len(casos); continue
        txt = p.read_text(encoding='utf-8')
        pend = []
        for x in casos:
            k = (fich, x['repo'].lower(), x['scan'].lower())
            hecho = False
            for ctx in ctxs.get(k, []):
                m = re.search(r'\[\[(.+?)\]\]', ctx)
                if not m: continue
                izq = ctx[:m.start()].strip(); der = ctx[m.end():].strip()
                pal = m.group(1).strip()
                buscado = ' '.join(y for y in (izq, pal, der) if y)
                norm, mapa = normaliza(txt)
                pos = [y.start() for y in re.finditer(re.escape(buscado), norm)]
                if len(pos) != 1: continue
                ini_n = pos[0] + (len(izq) + 1 if izq else 0)
                ini, fin = mapa[ini_n], mapa[ini_n + len(pal) - 1] + 1
                real = txt[ini:fin]
                if normaliza(real)[0] != pal: continue
                nuevo = x['scan'].capitalize() if real[:1].isupper() else x['scan']
                # EL CAMPO `scan` NO ES TEXTO PUBLICABLE. Es la normalizacion del
                # alineador: sin mayusculas, sin puntuacion, sin diacriticos y a
                # veces con el TITULILLO de la pagina pegado. Sirve para decidir,
                # no para escribir. Aplicado verbatim produjo (2026-08-09):
                #   'Nikolai'      -> 'Anti material worlds 35 nikolat'
                #   'Visakhapatnam'-> 'Visakha pat madhya lija ch 8 tana'
                #   'Bhuvar, (3) Svar' -> 'Bhuvah 3 svah'   (comas y parentesis)
                # Solo se acepta UNA palabra puramente alfabetica.
                if not re.fullmatch(r"[A-Za-z\u00c0-\u024f\u1e00-\u1eff'-]+", x['scan']):
                    fall['el scan no es una palabra limpia'] += 1
                    hecho = True
                    break
                # GUARDARRAIL DE DIACRITICOS, version corregida. El campo `repo`
                # de los candidatos viene SIN diacriticos, asi que el agente
                # juzgo 'gosvami' -> 'gosvamin' sin ver que el fichero tiene
                # 'Gosvami' con las dos largas. Sus propias notas lo demuestran:
                # el impreso dice «Sukadeva Gosvamin» CON diacriticos, y la
                # diferencia real es la -n final. Aplicar el campo `scan` tal
                # cual los borraria. Se apartan para resolverlos con la forma
                # acentuada que el propio agente cito.
                if dia(nuevo) < dia(real):
                    diacr.append({'file': fich, 'antes': real, 'scan_ocr': x['scan'],
                                  'page': x['pagina'], 'nota': x['nota']})
                    hecho = True
                    break
                pend.append((ini, fin, real, nuevo, x))
                hecho = True
                break
            if not hecho:
                fall['sin posicion unica'] += 1
                sinpos.append({'file': fich, 'repo': x['repo'], 'scan': x['scan'],
                               'page': x['pagina'], 'nota': x['nota']})
        for ini, fin, real, nuevo, x in sorted(pend, key=lambda y: -y[0]):
            txt = txt[:ini] + nuevo + txt[fin:]
            hechas.append({'file': fich, 'repo': real, 'scan': nuevo, 'page': x['pagina'],
                           'antes': real, 'despues': nuevo, 'nota': x['nota'][:200],
                           'quita_diacriticos': False})
        if aplicar and pend: p.write_text(txt, encoding='utf-8')

    json.dump(sinpos, open(HERE / 'pendientes_sin_posicion.json', 'w'), ensure_ascii=False, indent=1)
    print(f'\nsustituciones seguras: {len(hechas)} | sin posicion unica: {fall["sin posicion unica"]}')
    print(f'apartadas por no ser palabra limpia: {fall["el scan no es una palabra limpia"]}')
    print(f'apartadas por quitar diacriticos: {len(diacr)} -> pendientes_diacriticos.json')
    if aplicar:
        json.dump(hechas, open(HERE / 'aplicadas_imagen.json', 'w'), ensure_ascii=False, indent=1)
        json.dump(diacr, open(HERE / 'pendientes_diacriticos.json', 'w'), ensure_ascii=False, indent=1)
        json.dump(sinpos, open(HERE / 'pendientes_sin_posicion.json', 'w'), ensure_ascii=False, indent=1)
        with open(HERE / 'verdicts.jsonl', 'a') as fh:
            for h in hechas:
                fh.write(json.dumps({"repo": h['antes'].lower(), "scan": h['despues'].lower(),
                                     "estado": "VERIFICADO",
                                     "razon": f"adjudicado por IMAGEN del impreso (p.{h['page']}): {h['nota']}",
                                     "fecha": FECHA}, ensure_ascii=False) + '\n')
            for x in filas:
                if x['veredicto'] == 'IMPRESO=SCAN': continue
                est = {'IMPRESO=REPO': 'DESCARTADO', 'IMPRESO=OTRO': 'ABIERTO',
                       'NO_LEGIBLE': 'DESCARTADO', 'NO_ENCONTRADO': 'DESCARTADO'}[x['veredicto']]
                raz = f"{x['veredicto']} sobre la IMAGEN del impreso (p.{x['pagina']}): {x['nota'][:200]}"
                if x.get('real'): raz += f" | el impreso dice: {x['real'][:200]}"
                fh.write(json.dumps({"repo": x['repo'].lower(), "scan": x['scan'].lower(),
                                     "estado": est, "razon": raz, "fecha": FECHA},
                                    ensure_ascii=False) + '\n')
        otros = [x for x in filas if x['veredicto'] == 'IMPRESO=OTRO']
        json.dump(otros, open(HERE / 'tercera_lectura.json', 'w'), ensure_ascii=False, indent=1)
        print(f'veredictos escritos: {len(filas)} | terceras lecturas a resolver a mano: {len(otros)}')
    return 0


if __name__ == '__main__':
    sys.exit(main())
