#!/usr/bin/env python3
"""Escribe en el repo las frases transcritas del impreso.

Dos colas distintas que se resuelven igual, con una sustitucion EXACTA:

  RESTAURAR  el adjudicador copio del fichero la frase actual (`frase_repo`) y
             transcribio de la imagen la del impreso (`frase_impreso`). Son
             pasajes que la edicion digital reescribio: no basta con borrar un
             fragmento, hay que poner la frase del libro.
  SITUADO    el fragmento sobraba pero aparecia varias veces y no se podia
             situar. El adjudicador dio un `ancla` unica y como queda al
             quitarlo (`como_queda`).

En los dos casos la operacion es la misma: buscar una cadena que aparece UNA
vez y sustituirla por otra. Nada de expresiones regulares ni de limites de
palabra: el texto viene copiado letra a letra del fichero, asi que o casa exacto
o no se toca.

Guardarrailes:
  - UNICIDAD. Si el texto viejo no aparece exactamente una vez, se aparta. Dos
    apariciones significan que el adjudicador no delimito bien.
  - DIACRITICOS. Si la frase nueva trae MENOS diacriticos que la vieja, se
    aparta: es la firma de una transcripcion hecha de memoria y no de la imagen.
    Ese error ya obligo a revertir una tanda entera ('Bhaumāsura' -> 'Baumasura').
  - SIN SOLAPES. Dos cambios sobre el mismo tramo se descartan; escribir uno
    encima de otro fue lo que dejo 'seminaaaaa'.
  - TAMANO. Una frase nueva que triplica a la vieja no es una restauracion, es
    un error de delimitacion.

Uso: python3 aplicar_restauracion.py [--aplicar] [--muestra 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-11'


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


def pela(s):
    """La palabra sin sus diacriticos, para poder emparejarla."""
    return ''.join(c for c in unicodedata.normalize('NFD', s)
                   if unicodedata.category(c) != 'Mn').lower()


def pierde_diacriticos(viejo, nuevo):
    """Palabra que SOBREVIVE al cambio pero se ha quedado sin sus diacriticos.

    Contar diacriticos en total no vale: si la supresion se lleva una palabra
    sanscrita entera, el texto nuevo tiene legitimamente menos. Lo que no puede
    pasar es que una palabra que sigue ahi pierda las vocales largas — esa es la
    firma de una transcripcion hecha de memoria ('Bhaumāsura' -> 'Baumasura'),
    que ya obligo a revertir una tanda entera."""
    dv = collections.defaultdict(set)
    for w in re.findall(r'\w+', viejo, re.UNICODE):
        dv[pela(w)].add(w)
    for w in re.findall(r'\w+', nuevo, re.UNICODE):
        f = pela(w)
        if f in dv and dia(w) == 0 and any(dia(x) > 0 for x in dv[f]):
            return w
    return None


def carga():
    """Normaliza las dos formas de resultado a (viejo, nuevo, ficha)."""
    out = []
    for f in sorted(LOTES.glob('resultado*.json')):
        n = int(re.search(r'(\d+)', f.name).group(1))
        if n < 100: continue
        for r in json.load(open(f, encoding='utf-8')):
            if r.get('veredicto') == 'RESTAURAR' and r.get('frase_repo') and r.get('frase_impreso'):
                out.append((r['frase_repo'], r['frase_impreso'], r, 'restauracion'))
            elif r.get('veredicto') == 'SITUADO' and r.get('ancla') and r.get('como_queda'):
                out.append((r['ancla'], r['como_queda'], r, 'supresion situada'))
            elif (r.get('veredicto') == 'TRANSCRITO' and r.get('palabra_repo')
                  and r.get('impreso') and ' ' in (r['palabra_repo'] + r['impreso'])):
                # Transcripcion de VARIAS palabras. El aplicador de grafias las
                # rechaza —y hace bien: meter una frase por ahi convirtio 'he' en
                # '—though from a lowborn family—' y duplico medio parrafo en
                # cc-madhya-8.36—. Aqui si valen, porque la sustitucion es exacta
                # y pasa por unicidad, diacriticos y control de solapes.
                out.append((r['palabra_repo'], r['impreso'], r, 'transcripcion larga'))
    return out


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

    casos = carga()
    print(f'casos transcritos del impreso: {len(casos)}')
    print('  ' + '  '.join(f'{v} {k}' for k, v in
                           collections.Counter(c[3] for c in casos).most_common()))

    res = collections.Counter(); hechas = []; apartados = []
    porfich = collections.defaultdict(list)
    for viejo, nuevo, r, tipo in casos: porfich[r['file']].append((viejo, nuevo, r, tipo))

    for fich, lista in sorted(porfich.items()):
        p = REPO / fich
        if not p.exists():
            res['sin fichero'] += len(lista); continue
        txt = p.read_text(encoding='utf-8')
        pend = []
        for viejo, nuevo, r, tipo in lista:
            if viejo == nuevo:
                res['la frase ya es la del impreso'] += 1; continue
            ms = [m for m in re.finditer(re.escape(viejo), txt)]
            if len(ms) != 1:
                res[f'la frase aparece {len(ms)} veces'] += 1
                apartados.append({**r, 'motivo': f'aparece {len(ms)} veces'}); continue
            # El impreso puede traer MENOS diacriticos que el repo y ser lo
            # correcto: en Teachings of Queen Kunti la p.25 pone «kleso
            # 'dhikaras» —sin la silaba «ta» y sin acento en la s— frente al
            # «kleśo 'dhikataras» de Bhagavad-gita 12.5. Es errata del impreso,
            # y el impreso es el patron. El adjudicador que ha visto la pagina
            # lo declara con `impreso_pierde_diacriticos`; sin esa declaracion
            # el guardarrail sigue bloqueando, que es su trabajo.
            mala = None if r.get('impreso_pierde_diacriticos') \
                        else pierde_diacriticos(viejo, nuevo)
            if mala:
                res['la transcripcion pierde diacriticos'] += 1
                apartados.append({**r, 'motivo': f'la palabra {mala!r} pierde sus diacriticos'}); continue
            if len(nuevo) > 3 * len(viejo) + 40:
                res['la frase nueva es desproporcionada'] += 1
                apartados.append({**r, 'motivo': 'longitud desproporcionada'}); continue
            pend.append((ms[0].start(), ms[0].end(), viejo, nuevo, r, tipo))

        ocupados = []
        for ini, fin, viejo, nuevo, r, tipo in sorted(pend, key=lambda y: -y[0]):
            if any(not (fin <= a or ini >= b) for a, b in ocupados):
                res['solapa con otro cambio del mismo fichero'] += 1
                apartados.append({**r, 'motivo': 'solapa'}); continue
            ocupados.append((ini, fin))
            txt = txt[:ini] + nuevo + txt[fin:]
            hechas.append({'file': fich, 'tipo': tipo, 'page': r.get('pagina'),
                           'antes': viejo, 'despues': nuevo,
                           'nota': (r.get('nota') or '')[:240]})
        if aplicar and ocupados: p.write_text(txt, encoding='utf-8')

    res['ESCRITO'] = len(hechas)
    print()
    for k, v in res.most_common(): print(f'  {v:5}  {k}')

    print(f'\n--- muestra de {min(muestra, len(hechas))} ---')
    for h in hechas[:muestra]:
        print(f"\n{h['file']}  p.{h['page']}  [{h['tipo']}]")
        print(f"   repo: {h['antes'][:150]}")
        print(f"impreso: {h['despues'][:150]}")

    json.dump(hechas, open(HERE / 'restauraciones.json', 'w'), ensure_ascii=False, indent=1)
    json.dump(apartados, open(HERE / 'restauraciones_apartadas.json', 'w'), ensure_ascii=False, indent=1)
    if aplicar:
        with open(HERE / 'verdicts.jsonl', 'a') as fh:
            for h in hechas:
                fh.write(json.dumps({"repo": h['antes'][:120].lower(),
                                     "scan": h['despues'][:120].lower(),
                                     "estado": "VERIFICADO",
                                     "razon": f"TRANSCRITO de la imagen del impreso (p.{h['page']}): {h['nota']}",
                                     "fecha": FECHA}, ensure_ascii=False) + '\n')
        print(f'\nAPLICADO: {len(hechas)} | apartados: {len(apartados)}')
    else:
        print('\n(ensayo: no se ha escrito nada. Anade --aplicar)')
    return 0


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