"""Flagged-decision review HTML — the human view of the run's flagged cells.

It shows part of the ``<base>.flags.json`` record: flagged cells and dropped
header text. Unplaced footnote-marker flags and the fusion heading flags are
written to the flags record but are not rendered here.

One section per table with a flagged cell or dropped header text: the table as
printed (a page crop with the table's boundary drawn), the extraction output
with the flagged cells highlighted — red for a defect the page contradicts,
gold for a cell that could not be verified, green for a conventional label the
extraction added — and the reasoning recorded for each flag. Printed text that
reached no output cell (a table-level 'header_text_dropped' flag) is listed
with the reasoning, since it has no output cell to highlight.

Rendered from tables already extracted, no extraction of its own. The
stylesheet carries print rules (A4 landscape, one table per page), so printing
the HTML to PDF yields the same paginated document.
"""

from __future__ import annotations

from html import escape
from pathlib import Path
from typing import List, Optional, Sequence, Tuple

from PIL.Image import Image

from quber.core.extractors import ExtractedTable
from quber.core.extractors.base import CELL_STATUS_REFERENCE, GroundedCell
from quber.review.html import before_b64

DEFAULT_DPI = 150

# A cell carrying any status registered for inspection renders here, not
# only the failures. ('header_text_dropped' is table-level and never appears
# on a cell; it renders in the reasoning list instead. 'footnote_marker_unplaced'
# is also table-level, and this document does not render it.)
RENDERED_STATUSES = frozenset(s.code for s in CELL_STATUS_REFERENCE if s.inspect)

CSS = """
@page { size: A4 landscape; margin: 13mm; }
body { font-family: 'Liberation Serif', serif; color: #1a1a1a; font-size: 10.5pt; }
h1 { font-size: 13.5pt; color: #1f3756; border-bottom: 2px solid #46647f; padding-bottom: 4px; }
h2 { font-size: 11pt; color: #46647f; margin: 10px 0 3px 0; }
.pagebreak { page-break-before: always; }
p, li { line-height: 1.45; }
img.capture { max-width: 100%; max-height: 300px; border: 1px solid #ccc; }
table.md { border-collapse: collapse; width: 100%; margin-top: 6px; font-size: 7.2pt; }
table.md th { background: #1f3756; color: #fff; padding: 3px 5px; text-align: left; font-size: 7pt; }
table.md td { border: 1px solid #b9c6d4; padding: 2.5px 5px; }
table.md tr:nth-child(even) td { background: #eef3f9; }
table.md th.defect, table.md td.defect { background: #f7d9d9; color: #7a1212; border: 2px solid #c81e1e; }
table.md th.unverified, table.md td.unverified { background: #f5e3bb; color: #6b4e0e; border: 2px solid #bd8b1c; }
table.md th.total_label_added, table.md td.total_label_added,
table.md th.header_label_added, table.md td.header_label_added
  { background: #e2efe5; color: #205231; border: 2px solid #2f6b3f; }
span.defect { color: #c81e1e; font-weight: bold; }
span.unverified { color: #bd8b1c; font-weight: bold; }
span.total_label_added, span.header_label_added { color: #2f6b3f; font-weight: bold; }
span.header_text_dropped { color: #46647f; font-weight: bold; }
"""


def render_flag_page(pdf: Path, page: int, dpi: int) -> Optional[Image]:
    """Rasterize one page of the PDF; None when the page cannot be rendered."""
    from pdf2image import convert_from_path

    imgs = convert_from_path(str(pdf), dpi=dpi, fmt="png", first_page=page, last_page=page)
    return imgs[0] if imgs else None


def _grid_html(grid: List[List[GroundedCell]]) -> str:
    rows = []
    for r, row in enumerate(grid):
        tag = "th" if r == 0 else "td"
        cells = []
        for cell in row:
            cls = f' class="{cell.status}"' if cell.status in RENDERED_STATUSES else ""
            cells.append(f"<{tag}{cls}>{escape(cell.text) or '&nbsp;'}</{tag}>")
        rows.append("<tr>" + "".join(cells) + "</tr>")
    return '<table class="md">' + "".join(rows) + "</table>"


def _reasoning_html(flagged: List[Tuple[int, int, GroundedCell]], dropped: Sequence[str]) -> str:
    items = []
    for r, c, cell in flagged:
        note = escape(cell.note or "(no inspector evidence recorded)")
        items.append(
            f"<li><b>({r},{c})</b> <span class='{cell.status}'>{cell.status}</span> "
            f"'{escape(cell.text)}': {note}</li>"
        )
    for fragment in dropped:
        items.append(
            f"<li><b>(table)</b> <span class='header_text_dropped'>header_text_dropped</span> "
            f"'{escape(fragment)}': printed above the table's first value row but carried "
            "into no output cell.</li>"
        )
    return "<ul>" + "".join(items) + "</ul>"


def render_flags_html(
    items: Sequence[Tuple[Path, Sequence[ExtractedTable]]],
    out_path: Path,
    *,
    dpi: int = DEFAULT_DPI,
) -> Optional[Path]:
    """Render the flagged-decision review document for already-extracted PDFs.

    ``items`` pairs each source PDF with its extracted tables, the same shape
    ``render_review_html`` takes. Only tables carrying at least one flagged
    decision (a cell whose status is registered for inspection, or dropped
    printed text) get a section; when no table qualifies, nothing is written
    and None is returned.
    """
    sections: List[str] = []
    for pdf, tables in items:
        for ft in tables:
            flagged = [
                (r, c, cell)
                for r, row in enumerate(ft.corrected_grid or [])
                for c, cell in enumerate(row)
                if cell.status in RENDERED_STATUSES
            ]
            if not flagged and not ft.dropped_text:
                continue

            box = ft.content_region or ft.som_region
            crop_region = ft.som_region or ft.content_region
            img = render_flag_page(pdf, ft.page, dpi) if box and crop_region else None
            img_html = (
                f"<img class='capture' src='data:image/png;base64,{before_b64(img, box, crop_region)}'>"
                if img is not None and box and crop_region
                else "<p><em>(no page image available)</em></p>"
            )
            heading = escape(f"{ft.table_id or f'p{ft.page}'}: {ft.title or '(untitled)'}")
            sections.append(
                "<div class='pagebreak'>"
                f"<h1>{heading}</h1>"
                f"<h2>As printed (page {ft.page})</h2>{img_html}"
                "<h2>Extraction output (red = defect, gold = unverified, green = added label)</h2>"
                f"{_grid_html(ft.corrected_grid)}"
                f"<h2>Flagged decisions</h2>{_reasoning_html(flagged, ft.dropped_text)}"
                "</div>"
            )

    if not sections:
        return None

    intro = (
        "<p>Each section shows one table with flagged decisions: the table as printed, the "
        "extraction output with the flagged cells highlighted — red where the page contradicts "
        "the output (defect), gold where the output could not be verified against the page "
        "(unverified), green where the extraction added a conventional label the page does not "
        "print (total_label_added / header_label_added) — and the reasoning recorded for each. "
        "Printed text that reached no output cell is listed as 'header_text_dropped'.</p>"
    )
    out_path.write_text(
        f"<!doctype html><html><head><meta charset='utf-8'><style>{CSS}</style></head><body>"
        f"<h1>Review queue — flagged extraction decisions</h1>{intro}" + "".join(sections) + "</body></html>",
        encoding="utf-8",
    )
    return out_path
