#!/usr/bin/env python3
"""
Genera un HTML de revisión con los diffs reales adjudicados por Claude.
Para cada entrada is_real_diff=true muestra:
  - Contexto de repo (palabra diferente resaltada)
  - Texto del scan OCR (surya o psm6)
  - Imagen de la página del scan incrustada en base64
  - Veredicto del LLM

Uso:
  python3 build_diff_report.py [--books iso ej ssr] [--out review_diffs.html]
"""
import argparse, base64, json, re, subprocess, tempfile
from pathlib import Path

HERE = Path(__file__).resolve().parent
CFG = json.loads((HERE / "books.json").read_text())
SCAN_ROOT = Path(CFG["_scan_root"])
OUT = HERE / "out"
OCR_DIR = HERE / "ocr"
SURYA_DIR = HERE / "surya"

STEM2PDF = {}
for book, rels in CFG["books"].items():
    for rel in rels:
        p = SCAN_ROOT / rel
        STEM2PDF.setdefault(book, {})[p.stem] = p

def page_png_b64(book, pageref, dpi=150):
    if not pageref or "#" not in pageref:
        return None
    stem, pg = pageref.split("#", 1)
    m = re.search(r'(\d+)', pg)
    if not m:
        return None
    pageno = int(m.group(1))
    pdf = STEM2PDF.get(book, {}).get(stem)
    if not pdf or not pdf.exists():
        return None
    with tempfile.TemporaryDirectory() as td:
        prefix = f"{td}/pg"
        r = subprocess.run(
            ["pdftoppm", "-f", str(pageno), "-l", str(pageno),
             "-r", str(dpi), "-png", str(pdf), prefix],
            capture_output=True, timeout=30)
        imgs = sorted(Path(td).glob("pg*.png"))
        if not imgs:
            return None
        return base64.b64encode(imgs[0].read_bytes()).decode()

def page_ocr(book, pageref):
    if not pageref or "#" not in pageref:
        return ""
    stem, pg = pageref.split("#", 1)
    sp = SURYA_DIR / book / stem / f"{pg}.surya.txt"
    if sp.exists():
        return sp.read_text(errors="ignore")
    tp = OCR_DIR / book / stem / f"{pg}.psm6.txt"
    if tp.exists():
        return tp.read_text(errors="ignore")
    # flat layout (iso, ej use flat)
    tp2 = OCR_DIR / book / f"{pg}.psm6.txt"
    return tp2.read_text(errors="ignore") if tp2.exists() else ""

def esc(s):
    return (s or "").replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")

def highlight_context(context, repo_word):
    """Resalta [[repo_word]] en el contexto."""
    pattern = re.escape(f"[[{repo_word}]]")
    highlighted = re.sub(
        pattern,
        f'<mark class="repo">[[{esc(repo_word)}]]</mark>',
        esc(context)
    )
    return highlighted

def highlight_ocr(ocr_text, scan_word, orig_reading):
    """Resalta la palabra del scan en el OCR text."""
    text = esc(ocr_text)
    # Try to highlight the original_reading from LLM
    word = orig_reading or scan_word or ""
    if word and len(word) > 2:
        pattern = re.compile(re.escape(word), re.IGNORECASE)
        text = pattern.sub(lambda m: f'<mark class="scan">{esc(m.group(0))}</mark>', text, count=3)
    return text

BOOK_LABELS = {
    "isopanisad": "Śrī Īśopaniṣad",
    "easy-journey-to-other-planets": "Easy Journey to Other Planets",
    "science-of-self-realization": "Science of Self-Realization",
    "teachings-of-queen-kunti": "Teachings of Queen Kuntī",
    "teachings-of-lord-caitanya": "Teachings of Lord Caitanya",
    "topmost-yoga-system": "The Topmost Yoga System",
    "beyond-birth-and-death": "Beyond Birth and Death",
    "nectar-of-devotion": "Nectar of Devotion",
    "nectar-of-instruction": "Nectar of Instruction",
    "light-of-the-bhagavata": "Light of the Bhāgavata",
    "on-the-way-to-krsna": "On the Way to Kṛṣṇa",
    "path-of-perfection": "Path of Perfection",
    "perfection-of-yoga": "Perfection of Yoga",
    "raja-vidya": "Rāja-vidyā",
    "elevation-to-krsna-consciousness": "Elevation to Kṛṣṇa Consciousness",
    "perfect-questions-perfect-answers": "Perfect Questions, Perfect Answers",
    "krsna-the-supreme-personality-of-godhead": "Kṛṣṇa Book",
    "srimad-bhagavatam": "Śrīmad-Bhāgavatam",
    "sri-caitanya-caritamrta": "Śrī Caitanya-caritāmṛta",
}

CSS = """
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Georgia, serif; font-size: 15px; background: #f5f4f0; color: #222; }
h1 { padding: 1.2rem 1.5rem; background: #2c1a0e; color: #e8d9c0; font-size: 1.4rem; }
h1 span { color: #c9a96e; font-size: 1rem; font-weight: normal; margin-left: 1rem; }
.toolbar { position: sticky; top: 0; z-index: 100; background: #3a2010;
           padding: .6rem 1.5rem; display: flex; align-items: center; gap: .8rem;
           border-bottom: 2px solid #c9a96e; }
.toolbar button { padding: .4rem .9rem; border: none; border-radius: 4px;
                  cursor: pointer; font-size: 13px; font-weight: bold; }
.btn-selall  { background: #c9a96e; color: #2c1a0e; }
.btn-selall:hover { background: #e0bf85; }
.btn-clrall  { background: #555; color: #eee; }
.btn-clrall:hover { background: #777; }
.btn-export  { background: #2e7d32; color: #fff; }
.btn-export:hover { background: #388e3c; }
.btn-reject  { background: #b71c1c; color: #fff; }
.btn-reject:hover { background: #c62828; }
#sel-count   { color: #f0d090; font-size: 13px; margin-left: .5rem; }
.book-section { margin: 1.5rem auto; max-width: 1500px; padding: 0 1rem; }
.book-header { background: #4a2c0e; color: #f0e0c0; padding: .6rem 1rem;
               border-radius: 6px 6px 0 0; font-size: 1.1rem; font-weight: bold; }
.book-header span { float: right; color: #c9a96e; font-size: .9rem; font-weight: normal; }
.entry { display: grid; grid-template-columns: 36px 1fr 1fr 300px; gap: 0;
         border: 1px solid #ccc; border-top: none; background: #fff; transition: background .15s; }
.entry.checked { background: #f0fff0; }
.entry.rejected { background: #fff0f0; opacity: .6; }
.entry + .entry { border-top: 1px solid #eee; }
.col-cb { display: flex; align-items: flex-start; justify-content: center;
          padding: .8rem .4rem; border-right: 1px solid #eee; background: #fafaf8; }
.col-cb input[type=checkbox] { width: 18px; height: 18px; cursor: pointer; margin-top: 4px; }
.col { padding: .8rem 1rem; }
.col-repo { border-right: 1px solid #ddd; }
.col-ocr  { border-right: 1px solid #ddd; font-family: monospace; font-size: 13px;
             white-space: pre-wrap; color: #333; max-height: 300px; overflow-y: auto; }
.col-img  { text-align: center; background: #f9f8f5; }
.col-img img { max-width: 100%; border: 1px solid #ccc; }
.col-img .no-img { color: #999; font-size: 12px; padding-top: 2rem; }
.label { font-size: 11px; color: #888; text-transform: uppercase;
         letter-spacing: .05em; margin-bottom: .4rem; }
.ref  { font-size: 12px; color: #666; margin-bottom: .5rem; font-style: italic; }
.ctx  { line-height: 1.6; }
mark.repo { background: #ffe0b2; padding: 0 2px; border-radius: 2px; }
mark.scan { background: #c8e6c9; padding: 0 2px; border-radius: 2px; }
.verdict { margin-top: .6rem; font-size: 13px; }
.verdict .diff { color: #b71c1c; font-weight: bold; }
.verdict .orig { color: #1b5e20; font-weight: bold; }
.verdict .conf { color: #666; font-size: 11px; margin-left: .5rem; }
.note { color: #555; font-size: 12px; margin-top: .3rem; font-style: italic; }
.col-header { background: #f0ebe2; font-size: 11px; font-weight: bold;
               text-transform: uppercase; letter-spacing: .06em;
               color: #666; padding: .4rem 1rem; border-top: 1px solid #ddd; }
.col-header.last { border-right: none; }
#export-out { display:none; position:fixed; top:0; left:0; right:0; bottom:0;
              background: rgba(0,0,0,.6); z-index:200; align-items:center; justify-content:center; }
#export-out.open { display:flex; }
.export-box { background:#fff; border-radius:8px; padding:1.5rem; max-width:700px; width:90%;
              max-height:80vh; display:flex; flex-direction:column; gap:.8rem; }
.export-box h2 { font-size:1rem; color:#333; }
.export-box textarea { flex:1; min-height:300px; font-family:monospace; font-size:12px;
                        border:1px solid #ccc; padding:.5rem; resize:vertical; }
.export-box .btn-row { display:flex; gap:.6rem; }
.export-box button { padding:.4rem .9rem; border:none; border-radius:4px; cursor:pointer; font-size:13px; }
.btn-copy { background:#1565c0; color:#fff; font-weight:bold; }
.btn-close { background:#888; color:#fff; }
"""

JS = """
function updateCount() {
  var n = document.querySelectorAll('.entry-cb:checked').length;
  document.getElementById('sel-count').textContent = n + ' seleccionados';
}
function selAll() {
  document.querySelectorAll('.entry-cb').forEach(function(cb){
    cb.checked = true;
    cb.closest('.entry').classList.add('checked');
    cb.closest('.entry').classList.remove('rejected');
  });
  updateCount();
}
function clrAll() {
  document.querySelectorAll('.entry-cb').forEach(function(cb){
    cb.checked = false;
    cb.closest('.entry').classList.remove('checked');
  });
  updateCount();
}
function onCb(cb) {
  var entry = cb.closest('.entry');
  if (cb.checked) {
    entry.classList.add('checked');
    entry.classList.remove('rejected');
  } else {
    entry.classList.remove('checked');
  }
  updateCount();
}
function rejectUnselected() {
  document.querySelectorAll('.entry-cb').forEach(function(cb){
    var entry = cb.closest('.entry');
    if (!cb.checked) entry.classList.add('rejected');
    else entry.classList.remove('rejected');
  });
}
function exportSelected() {
  var rows = [];
  document.querySelectorAll('.entry-cb:checked').forEach(function(cb){
    var entry = cb.closest('.entry');
    rows.push(JSON.parse(entry.dataset.payload));
  });
  if (!rows.length) { alert('No hay ninguno seleccionado.'); return; }
  document.getElementById('export-text').value = JSON.stringify(rows, null, 2);
  document.getElementById('export-out').classList.add('open');
}
function copyExport() {
  var ta = document.getElementById('export-text');
  ta.select();
  document.execCommand('copy');
  document.querySelector('.btn-copy').textContent = '¡Copiado!';
  setTimeout(function(){ document.querySelector('.btn-copy').textContent = 'Copiar al portapapeles'; }, 2000);
}
function closeExport() {
  document.getElementById('export-out').classList.remove('open');
}
"""

def render_entry(idx, entry, book, img_b64):
    llm = entry.get("llm", {}) or {}
    repo = entry.get("repo", "")
    orig = llm.get("original_reading", entry.get("scan", ""))
    note = llm.get("note", "")
    conf = llm.get("confidence", "")
    context = entry.get("context", "")
    ocr = page_ocr(book, entry.get("page", ""))
    ref = entry.get("file", "").replace(".md", "")

    ctx_html = highlight_context(context, repo)
    ocr_html = highlight_ocr(ocr[:3000], entry.get("scan", ""), orig)

    img_html = ""
    if img_b64:
        img_html = f'<img src="data:image/png;base64,{img_b64}" alt="scan page">'
    else:
        img_html = '<div class="no-img">imagen no disponible</div>'

    # Payload for export (minimal data needed to apply the fix)
    payload = json.dumps({
        "book": book,
        "file": entry.get("file", ""),
        "ref": entry.get("ref", ""),
        "page": entry.get("page", ""),
        "repo": repo,
        "original_reading": orig,
        "confidence": conf,
        "note": note,
    }, ensure_ascii=False)

    return f"""
<div class="entry" data-payload='{payload.replace("'", "&#39;")}'>
  <div class="col-cb">
    <input type="checkbox" class="entry-cb" onchange="onCb(this)" title="Aplicar esta corrección">
  </div>
  <div class="col col-repo">
    <div class="label">#{idx} — Repo</div>
    <div class="ref">{esc(ref)}</div>
    <div class="ctx">{ctx_html}</div>
    <div class="verdict">
      <span class="diff">REPO: «{esc(repo)}»</span>
      &rarr;
      <span class="orig">SCAN: «{esc(orig)}»</span>
      <span class="conf">[{esc(conf)}]</span>
    </div>
    <div class="note">{esc(note)}</div>
  </div>
  <div class="col col-ocr"><div class="label">OCR página</div>{ocr_html}</div>
  <div class="col col-img">{img_html}</div>
</div>"""

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--books", nargs="*")
    ap.add_argument("--out", default=str(HERE / "review_diffs.html"))
    a = ap.parse_args()

    books = a.books or list(CFG["books"].keys())

    all_entries = []  # (book, entry) pairs
    for book in books:
        f = OUT / f"{book}.adjudicated.jsonl"
        if not f.exists():
            continue
        for l in f.open():
            e = json.loads(l)
            llm = e.get("llm", {}) or {}
            if isinstance(llm, dict) and llm.get("is_real_diff"):
                all_entries.append((book, e))

    total = len(all_entries)
    print(f"Total diffs reales: {total}")

    # Group by book
    by_book = {}
    for book, e in all_entries:
        by_book.setdefault(book, []).append(e)

    # Cache rendered page images per (book, pageref)
    page_cache = {}

    sections_html = []
    global_idx = 0
    for book in books:
        entries = by_book.get(book, [])
        if not entries:
            continue
        label = BOOK_LABELS.get(book, book)
        print(f"  {book}: {len(entries)} diffs — rendering pages...")
        rows = []
        for e in entries:
            global_idx += 1
            pref = e.get("page", "")
            if pref not in page_cache:
                print(f"    page {pref}...", end=" ", flush=True)
                page_cache[pref] = page_png_b64(book, pref, dpi=120)
                print("ok" if page_cache[pref] else "no image")
            rows.append(render_entry(global_idx, e, book, page_cache[pref]))

        col_headers = """
<div style="display:grid;grid-template-columns:36px 1fr 1fr 300px;border:1px solid #ccc;border-top:none">
  <div class="col-header"></div>
  <div class="col-header">Contexto del repo (marcado en naranja)</div>
  <div class="col-header">OCR del scan (marcado en verde)</div>
  <div class="col-header last">Imagen del scan</div>
</div>"""
        sections_html.append(f"""
<div class="book-section">
  <div class="book-header">{esc(label)}<span>{len(entries)} diffs</span></div>
  {col_headers}
  {"".join(rows)}
</div>""")

    toolbar = """
<div class="toolbar">
  <button class="btn-selall" onclick="selAll()">✓ Seleccionar todos</button>
  <button class="btn-clrall" onclick="clrAll()">✕ Limpiar selección</button>
  <button class="btn-reject" onclick="rejectUnselected()">Tachar no seleccionados</button>
  <button class="btn-export" onclick="exportSelected()">⬇ Exportar seleccionados (JSON)</button>
  <span id="sel-count">0 seleccionados</span>
</div>
<div id="export-out">
  <div class="export-box">
    <h2>Correcciones seleccionadas (JSON) — pega esto para aplicar al repo</h2>
    <textarea id="export-text" readonly></textarea>
    <div class="btn-row">
      <button class="btn-copy" onclick="copyExport()">Copiar al portapapeles</button>
      <button class="btn-close" onclick="closeExport()">Cerrar</button>
    </div>
  </div>
</div>"""

    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Scan diff review — vedabase-original</title>
<style>{CSS}</style>
</head>
<body>
<h1>Scan diff review — vedabase-original <span>{total} diffs reales · {len(by_book)} libros</span></h1>
{toolbar}
{"".join(sections_html)}
<script>{JS}</script>
</body>
</html>"""

    Path(a.out).write_text(html, encoding="utf-8")
    print(f"\nEscrito: {a.out}")

if __name__ == "__main__":
    main()
