"""Before/after extraction review HTML.

One row per table, two columns: the page cropped around the table with its
Set-of-Mark bounding box drawn (``before``), beside the final corrected
markdown (``after``). The tables are supplied already-extracted, so this does
no extraction of its own -- it only renders what the caller passes in.
"""

from __future__ import annotations

import base64
import io
from html import escape
from pathlib import Path
from typing import Dict, List, Sequence, Tuple

from PIL import ImageDraw
from PIL.Image import Image

from quber.core.extractors import ExtractedTable

Box = Tuple[float, float, float, float]
DEFAULT_DPI = 150

CSS = """
body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;margin:24px;color:#1a1a1a;background:#fafafa}
h1{font-size:22px} h2{margin-top:40px;border-bottom:2px solid #ccc;padding-bottom:4px}
h3{margin:28px 0 6px;font-size:15px}
.meta{color:#666;font-size:12px;margin-bottom:6px}
.row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:16px;align-items:start;
  border:1px solid #e0e0e0;border-radius:8px;padding:12px;background:#fff;margin-bottom:18px}
.col{min-width:0;overflow-x:auto}
.col h4{margin:0 0 6px;font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:#888}
.col img{max-width:100%;border:1px solid #ddd}
table.t{border-collapse:collapse;font-size:11px;width:100%}
table.t th,table.t td{border:1px solid #ccc;padding:2px 5px;text-align:right;vertical-align:top}
table.t th{background:#f0f0f0;font-weight:600}
.legend{font-size:12px;color:#555;margin:8px 0 20px}
.nomatch{color:#b00;font-style:italic}
.units{color:#0a5;font-weight:600}
@media(max-width:900px){.row{grid-template-columns:1fr}.col img{max-width:none;width:100%}}
"""


def render_pages(pdf: Path, dpi: int) -> Dict[int, Image]:
    """Rasterize every page of the PDF to a PIL image keyed by 1-indexed page."""
    from pdf2image import convert_from_path

    imgs = convert_from_path(str(pdf), dpi=dpi, fmt="png")
    return dict(enumerate(imgs, start=1))


def before_b64(img: Image, box: Box, crop_region: Box) -> str:
    """Draw `box` on the page and crop around `crop_region` (both normalized
    0..1, top-left), with a small margin so the box is inset and visible.

    `box` is the table's final boundary (content_region — the corrected end of
    the table at its last data row). The crop is taken around the broader
    `crop_region` (som_region) so the area just below the box, where footnotes
    sit, stays visible and the box is seen to correctly exclude it."""
    w_px, h_px = img.size
    cx0, cy0, cx1, cy1 = crop_region
    cleft, cright = min(cx0, cx1), max(cx0, cx1)
    ctop, cbottom = min(cy0, cy1), max(cy0, cy1)
    mx, my = 0.025, 0.012  # crop margin as a fraction of the page
    crop_box = (
        int(max(0.0, cleft - mx) * w_px),
        int(max(0.0, ctop - my) * h_px),
        int(min(1.0, cright + mx) * w_px),
        int(min(1.0, cbottom + my) * h_px),
    )
    bx0, by0, bx1, by1 = box
    bleft, bright = min(bx0, bx1), max(bx0, bx1)
    btop, bbottom = min(by0, by1), max(by0, by1)
    canvas = img.copy()
    ImageDraw.Draw(canvas).rectangle(
        [bleft * w_px, btop * h_px, bright * w_px, bbottom * h_px], outline=(0, 90, 235), width=3
    )
    crop = canvas.crop(crop_box) if crop_box[2] > crop_box[0] and crop_box[3] > crop_box[1] else canvas
    buf = io.BytesIO()
    crop.save(buf, "PNG")
    return base64.b64encode(buf.getvalue()).decode()


def grid_html(cells: List[List[str]]) -> str:
    if not cells:
        return "<em>(empty)</em>"
    width = max(len(r) for r in cells)
    rows = []
    for i, r in enumerate(cells):
        padded = [escape(str(c)) for c in r] + [""] * (width - len(r))
        tag = "th" if i == 0 else "td"
        rows.append("<tr>" + "".join(f"<{tag}>{c}</{tag}>" for c in padded) + "</tr>")
    return '<table class="t">' + "".join(rows) + "</table>"


def md_to_html(md: str) -> str:
    rows = []
    for line in md.splitlines():
        line = line.strip()
        if not line.startswith("|"):
            continue
        cells = [c.strip() for c in line.strip("|").split("|")]
        if cells and all(set(c) <= {"-", ":", " "} for c in cells):
            continue  # delimiter row
        rows.append(cells)
    return grid_html(rows)


def render_review_html(
    items: Sequence[Tuple[Path, Sequence[ExtractedTable]]],
    out_path: Path,
    *,
    dpi: int = DEFAULT_DPI,
) -> Path:
    """Render the before/after review HTML for one or more already-extracted PDFs.

    ``items`` pairs each source PDF with the tables already extracted from it.
    One ``<h2>`` section per PDF, one row per table. Returns ``out_path``.
    """
    parts = [
        "<!DOCTYPE html><html><head><meta charset='utf-8'>",
        "<meta name='viewport' content='width=device-width, initial-scale=1'>",
        f"<style>{CSS}</style></head><body>",
        "<h1>Set-of-Mark extraction review &mdash; before (table + bounding box) | after (final markdown)</h1>",
        "<div class='legend'>Left: the page cropped around the table with its final boundary "
        "(content_region, the corrected end of the table) drawn; the crop extends a little below so "
        "any footnotes sit visibly outside the box. Right: the final corrected markdown. One row per table.</div>",
    ]

    for pdf, tables in items:
        images = render_pages(pdf, dpi)
        parts.append(f"<h2>{escape(pdf.name)}</h2>")
        parts.append(f"<div class='meta'>{len(tables)} tables</div>")

        for idx, ft in enumerate(tables, start=1):
            img = images.get(ft.page)
            # Draw the final boundary (content_region); crop around the broader
            # som_region so the footnote area below the box stays visible.
            box = ft.content_region or ft.som_region
            crop_region = ft.som_region or ft.content_region
            before = before_b64(img, box, crop_region) if (img is not None and box and crop_region) else None
            title = escape(ft.title or "(untitled)")
            parts.append(f"<h3>p{ft.page} &middot; table {idx} &middot; {title}</h3>")
            units = f"<span class='units'>{escape(ft.units)}</span>" if ft.units else "(none)"
            parts.append(
                f"<div class='meta'>units={units} &middot; flavor={ft.flavor} &middot; "
                f"camelot_accuracy={ft.camelot_accuracy:.1f} &middot; llm_corrected={ft.llm_corrected}</div>"
            )
            img_html = (
                f"<img src='data:image/png;base64,{before}'>"
                if before
                else "<span class='nomatch'>no Set-of-Mark region</span>"
            )
            parts.append(
                "<div class='row'>"
                f"<div class='col'><h4>before &mdash; table + bounding box</h4>{img_html}</div>"
                f"<div class='col'><h4>after &mdash; final markdown</h4>{md_to_html(ft.markdown)}</div>"
                "</div>"
            )

    parts.append("</body></html>")
    out_path.write_text("".join(parts))
    return out_path
