#!/usr/bin/env python3
"""Check the generated cover letter against its style spec and one-page fit.

    python3 profile/cover-letter-verify.py
    python3 profile/cover-letter-verify.py --html applications/cleo/cover-letter.html

Renders the print layout in headless Chromium and asserts the numbers for the
style the HTML declares (plain or editorial), then exits non-zero on drift.
Run it after changing a cover-letter.yaml or the CSS in cover-letter-build.py.
"""
from __future__ import annotations

import argparse
import asyncio
from pathlib import Path

from playwright.async_api import async_playwright

ROOT = Path(__file__).resolve().parent
DEFAULT_HTML = ROOT / "cover-letter.html"

# A4 height in CSS pixels (297mm at 96dpi). The sheet must not exceed it,
# otherwise Chromium paginates the letter onto a second page.
A4_HEIGHT_PX = 297 / 25.4 * 96


async def measure(html_path: Path) -> dict:
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page(viewport={"width": 794, "height": 1123})
        await page.emulate_media(media="print")
        await page.goto(f"file://{html_path.resolve()}")
        await page.wait_for_timeout(500)
        data = await page.evaluate(
            """() => {
              const cs = (sel) => {
                const el = document.querySelector(sel);
                if (!el) return null;
                const s = getComputedStyle(el);
                const r = el.getBoundingClientRect();
                return {
                  fontSize: parseFloat(s.fontSize),
                  lineHeight: s.lineHeight === 'normal' ? null : parseFloat(s.lineHeight),
                  color: s.color,
                  fontFamily: s.fontFamily,
                  top: r.top, bottom: r.bottom, height: r.height,
                };
              };
              const colophon = document.querySelector('.colophon');
              const sheet = document.querySelector('.sheet');
              return {
                style: (document.querySelector('meta[name="document-style"]') || {}).content || 'plain',
                name: cs('.masthead-name') || cs('.sender .name'),
                title: cs('.doc-title'),
                body: cs('.body p'),
                sheet: cs('.sheet'),
                sheetScroll: sheet.scrollHeight,
                accent: getComputedStyle(document.documentElement)
                  .getPropertyValue('--accent').trim(),
                hasFooter: !!colophon,
                footerBottom: colophon ? Math.round(colophon.getBoundingClientRect().bottom) : null,
                paragraphs: document.querySelectorAll('.body > p').length,
                highlights: document.querySelectorAll('.highlight').length,
                highlightLabels: document.querySelectorAll('.highlight .highlight-label').length,
                fonts: [...document.fonts].map((f) => f.family + ':' + f.status),
              };
            }"""
        )
        await browser.close()
        return data


def check(data: dict) -> list[str]:
    problems: list[str] = []

    def expect(label: str, ok: bool, detail: str = "") -> None:
        mark = "PASS" if ok else "FAIL"
        print(f"  [{mark}] {label}{(' - ' + detail) if detail else ''}")
        if not ok:
            problems.append(label)

    style = data["style"]
    name, title, body, sheet = data["name"], data["title"], data["body"], data["sheet"]
    print(f"  [style] {style}")

    # --- shared checks (both styles) ---
    expect("body has 4-5 paragraphs", 4 <= data["paragraphs"] <= 5,
           f'{data["paragraphs"]} paragraphs')
    expect("exactly one highlight block", data["highlights"] == 1,
           f'{data["highlights"]} block(s)')
    expect("highlight block has a label", data["highlightLabels"] == 1)
    expect("fits one A4 page", data["sheetScroll"] <= A4_HEIGHT_PX + 0.5,
           f'{data["sheetScroll"]:.0f}px of {A4_HEIGHT_PX:.0f}px')

    if style == "editorial":
        expect("font Inter loaded", all("Inter:loaded" in f for f in data["fonts"]),
               ", ".join(data["fonts"]))
        expect("name 28-32px", name and 28 <= name["fontSize"] <= 32, f'{name["fontSize"]}px')
        expect("document title 20-24px", title and 20 <= title["fontSize"] <= 24,
               f'{title["fontSize"]}px')
        expect("body 15-16px", body and 15 <= body["fontSize"] <= 16, f'{body["fontSize"]}px')
        ratio = (body["lineHeight"] / body["fontSize"]) if body and body["lineHeight"] else 0
        expect("body line-height 1.5-1.6", 1.49 <= ratio <= 1.61, f"{ratio:.2f}")
        expect("single accent colour set", bool(data["accent"]), data["accent"])
        expect("footer present and inside the page",
               data["hasFooter"] and data["footerBottom"] <= A4_HEIGHT_PX + 0.5,
               f'footer bottom {data["footerBottom"]}px')
    else:  # plain
        expect("base font (no Inter)", all("Inter" not in f for f in data["fonts"]),
               ", ".join(data["fonts"]))
        expect("name is ordinary text (<=18px)", name and name["fontSize"] <= 18,
               f'{name["fontSize"]}px')
        expect("no document title", title is None)
        expect("no footer", data["hasFooter"] is False)
        expect("no accent colour", not data["accent"], data["accent"] or "(none)")
        expect("body 15-17px", body and 15 <= body["fontSize"] <= 17, f'{body["fontSize"]}px')
        ratio = (body["lineHeight"] / body["fontSize"]) if body and body["lineHeight"] else 0
        expect("body line-height 1.4-1.6", 1.39 <= ratio <= 1.61, f"{ratio:.2f}")

    return problems


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--html", default=str(DEFAULT_HTML), help="generated HTML to check")
    args = parser.parse_args()

    html_path = Path(args.html).resolve()
    if not html_path.exists():
        print(f"ERROR: {html_path} not found - run python3 profile/cover-letter-build.py")
        return 1

    print(f"Cover letter check: {html_path}")
    problems = check(asyncio.run(measure(html_path)))
    if problems:
        print(f"\nFAILED: {len(problems)} check(s): {', '.join(problems)}")
        return 1
    print("\nOK: style spec and one-page fit verified")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
