#!/usr/bin/env python3
"""Render the running Thermograph frontend across the breakpoint matrix.

Drives headless Chromium (Playwright) over the served app and writes one PNG per
page x width x color-scheme into frontend/.screenshots/ (gitignored), so a design
change can actually be seen. See DESIGN.md for the workflow.

Prereqs: the app must be serving (`make lan-run`) and Playwright + Chromium must
be installed (`make shots` handles both). Run directly for a narrow sweep:

    python tools/shoot.py                       # full matrix
    python tools/shoot.py index                 # one page, all widths/schemes
    python tools/shoot.py index --width 390 --scheme dark
    SHOTS_BASE=http://127.0.0.1:8137 python tools/shoot.py
"""
from __future__ import annotations

import argparse
import os
import sys
import urllib.request
from pathlib import Path

# Base URL of the served app (no trailing slash). Override with SHOTS_BASE.
BASE = os.environ.get("SHOTS_BASE", "http://127.0.0.1:8137").rstrip("/")

# Default output dir, relative to the repo root (this file lives in tools/).
OUT_DIR = Path(__file__).resolve().parent.parent / "frontend" / ".screenshots"

# The CLAUDE.md / DESIGN.md validation widths: phone, tablet, 1440p, 2K, 4K.
WIDTHS = [390, 800, 1920, 2560, 3840]
SCHEMES = ["dark", "light"]

# A stable, well-populated location (Seattle) so data-driven pages aren't blank.
# The frontend reads its spot from the URL hash (frontend/nav.js).
_LOC = "lat=47.58&lon=-122.398"
_DAY = f"{_LOC}&date=2026-07-10"

# page name -> path under BASE. Hash-driven pages carry a default location.
ROUTES = {
    "index": f"/#{_LOC}",
    "calendar": f"/calendar#{_LOC}",
    "compare": f"/compare#{_LOC}",
    "day": f"/day#{_DAY}",
    "score": f"/score#{_LOC}",
    "alerts": "/alerts",
    "legend": "/legend",
    "climate-hub": "/climate",
    "city": "/climate/seattle-washington-us",
}


def _server_up() -> bool:
    try:
        req = urllib.request.Request(BASE + "/", method="GET")
        with urllib.request.urlopen(req, timeout=4) as resp:
            return resp.status < 500
    except Exception:
        return False


def _parse_args() -> argparse.Namespace:
    ap = argparse.ArgumentParser(description="Screenshot the Thermograph frontend.")
    ap.add_argument("pages", nargs="*", choices=list(ROUTES), default=[],
                    help="page names to shoot (default: all). One or more of: "
                         + ", ".join(ROUTES))
    ap.add_argument("--width", type=int, action="append", metavar="PX",
                    help="viewport width; repeatable (default: all 5 breakpoints)")
    ap.add_argument("--scheme", choices=SCHEMES, action="append",
                    help="color scheme; repeatable (default: dark and light)")
    ap.add_argument("--out", type=Path, default=OUT_DIR,
                    help=f"output directory (default: {OUT_DIR})")
    return ap.parse_args()


def main() -> int:
    args = _parse_args()

    pages = args.pages or list(ROUTES)
    widths = args.width or WIDTHS
    schemes = args.scheme or SCHEMES

    if not _server_up():
        print(f"error: the app is not reachable at {BASE}\n"
              f"start it first with:  make lan-run\n"
              f"(or point elsewhere with SHOTS_BASE=...)", file=sys.stderr)
        return 1

    try:
        from playwright.sync_api import sync_playwright
    except ModuleNotFoundError:
        print("error: playwright is not installed.\n"
              "run `make shots` (installs it into .venv), or:  "
              "pip install -r tools/requirements.txt && playwright install chromium",
              file=sys.stderr)
        return 1

    args.out.mkdir(parents=True, exist_ok=True)
    shot_count = 0

    with sync_playwright() as p:
        browser = p.chromium.launch()
        try:
            for scheme in schemes:
                for width in widths:
                    # A fresh context per (scheme, width): color_scheme and viewport
                    # are context-level, and reduced motion freezes animations so the
                    # capture is the settled final frame.
                    ctx = browser.new_context(
                        viewport={"width": width, "height": 900},
                        color_scheme=scheme,
                        reduced_motion="reduce",
                        device_scale_factor=1,
                    )
                    page = ctx.new_page()
                    for name in pages:
                        url = BASE + ROUTES[name]
                        try:
                            page.goto(url, wait_until="domcontentloaded", timeout=30000)
                            try:
                                page.wait_for_load_state("networkidle", timeout=15000)
                            except Exception:
                                pass  # some pages keep a socket open; settle below
                            page.wait_for_timeout(1200)  # chart/layout settle
                            dest = args.out / f"{name}@{width}-{scheme}.png"
                            page.screenshot(path=str(dest), full_page=True)
                            shot_count += 1
                            print(f"  {dest.relative_to(args.out.parent)}")
                        except Exception as e:
                            print(f"  ! {name}@{width}-{scheme}: {e}", file=sys.stderr)
                    ctx.close()
        finally:
            browser.close()

    print(f"\n{shot_count} screenshot(s) -> {args.out}")
    return 0


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