#!/usr/bin/env python3
"""Build a compact mobile/browser runtime-controls smoke summary."""
from __future__ import annotations

import argparse
import html
import json
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
SOURCE_FILE = "mobile_browser_controls_smoke.json"
SUMMARY_FILE = "mobile_browser_controls_summary.json"
SUMMARY_HTML = "mobile_browser_controls_summary.html"


def load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8")) if path.exists() else {}


def compact_json_size(payload: Any) -> int:
    return len(json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8"))


def fallback_removed_summary(path: Path, summary_path: Path) -> dict[str, Any] | None:
    if path.exists() or not summary_path.exists():
        return None
    summary = load_json(summary_path)
    if not summary:
        return None
    recorded_size = int(
        summary.get("lastRecordedSourceSizeBytes")
        or summary.get("removedRawSizeBytes")
        or summary.get("sourceSizeBytes")
        or 0
    )
    summary.update(
        {
            "status": "raw-removed-summary-retained",
            "sourceSizeBytes": 0,
            "lastRecordedSourceSizeBytes": recorded_size,
            "removedRawSizeBytes": recorded_size,
            "rawFilePresent": False,
            "rawRegeneration": "Run tools/verify_mobile_browser_controls.py locally to regenerate the ignored raw smoke when debugging browser regressions.",
            "boundary": (
                "Compact browser runtime-controls smoke contract. It keeps the top-level "
                "status/search fields and checks used by movement, completion, and "
                "event/battle audit surfaces. The checked-in raw smoke was removed; "
                "the verifier can regenerate it locally as an ignored output."
            ),
        }
    )
    return summary


def build_snapshot_summary(snapshots: dict[str, Any]) -> dict[str, Any]:
    rows = []
    for key, value in sorted(snapshots.items()):
        rows.append(
            {
                "key": key,
                "type": type(value).__name__,
                "sizeBytes": compact_json_size(value),
            }
        )
    return {
        "count": len(rows),
        "totalSizeBytes": sum(int(row["sizeBytes"]) for row in rows),
        "largest": sorted(rows, key=lambda row: int(row["sizeBytes"]), reverse=True)[:20],
        "keys": [row["key"] for row in rows],
    }


def build_summary(root: Path = ROOT) -> dict[str, Any]:
    out_dir = root / "out"
    path = out_dir / SOURCE_FILE
    fallback = fallback_removed_summary(path, out_dir / SUMMARY_FILE)
    if fallback is not None:
        return fallback
    payload = load_json(path)
    controls = {key: value for key, value in payload.items() if key != "snapshots"}
    snapshots = payload.get("snapshots") if isinstance(payload.get("snapshots"), dict) else {}
    controls["sourceFile"] = f"out/{SOURCE_FILE}"
    controls["snapshots"] = build_snapshot_summary(snapshots)
    compact_size = compact_json_size(controls)
    return {
        "kind": "hwanse-mobile-browser-controls-summary",
        "source": "tools/summarize_mobile_browser_controls.py",
        "status": "ready",
        "sourceFile": f"out/{SOURCE_FILE}",
        "boundary": (
            "Compact browser runtime-controls smoke contract. It keeps the top-level "
            "status/search fields and checks used by movement, completion, and "
            "event/battle audit surfaces, while replacing raw browser snapshots with "
            "a key/size summary. The raw smoke is verifier output and should not be "
            "committed after regeneration."
        ),
        "sourceSizeBytes": path.stat().st_size if path.exists() else 0,
        "compactSizeBytes": compact_size,
        "controls": controls,
    }


def html_page(summary: dict[str, Any]) -> str:
    controls = summary["controls"]
    raw_line = f"{int(summary['sourceSizeBytes']):,} bytes raw"
    if summary.get("lastRecordedSourceSizeBytes"):
        raw_line += f" (removed; last recorded {int(summary['lastRecordedSourceSizeBytes']):,} bytes)"
    scalar_rows = []
    for key, value in controls.items():
        if key in {"checks", "snapshots"}:
            continue
        scalar_rows.append(
            "<tr>"
            f"<td><code>{html.escape(key)}</code></td>"
            f"<td>{html.escape(str(value))[:1200]}</td>"
            "</tr>"
        )
    check_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(key)}</code></td>"
        f"<td>{html.escape(str(value))[:1600]}</td>"
        "</tr>"
        for key, value in (controls.get("checks") or {}).items()
    )
    snapshot_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(row['key'])}</code></td>"
        f"<td>{int(row['sizeBytes']):,}</td>"
        f"<td>{html.escape(row['type'])}</td>"
        "</tr>"
        for row in (controls.get("snapshots") or {}).get("largest", [])
    )
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>Mobile Browser Controls Summary</title>
  <style>
    body {{ margin: 24px; color: #202124; font-family: system-ui, sans-serif; line-height: 1.5; }}
    table {{ width: 100%; border-collapse: collapse; margin: 14px 0 28px; }}
    th, td {{ border: 1px solid #d7dce2; padding: 6px 8px; text-align: left; vertical-align: top; }}
    th {{ background: #f4f6f8; }}
    code {{ background: #f4f6f8; padding: 1px 4px; }}
  </style>
</head>
<body>
  <h1>Mobile Browser Controls Summary</h1>
  <p>{html.escape(summary['boundary'])}</p>
  <p><code>{html.escape(summary['sourceFile'])}</code>: {html.escape(raw_line)}, {int(summary['compactSizeBytes']):,} bytes compact.</p>
  <h2>Top-Level Fields</h2>
  <table>
    <thead><tr><th>field</th><th>value</th></tr></thead>
    <tbody>{''.join(scalar_rows)}</tbody>
  </table>
  <h2>Checks</h2>
  <table>
    <thead><tr><th>check</th><th>value</th></tr></thead>
    <tbody>{check_rows}</tbody>
  </table>
  <h2>Largest Raw Snapshots</h2>
  <table>
    <thead><tr><th>snapshot</th><th>raw bytes</th><th>type</th></tr></thead>
    <tbody>{snapshot_rows}</tbody>
  </table>
</body>
</html>
"""


def write_outputs(summary: dict[str, Any], out_dir: Path = OUT, html_out: Path | None = None) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / SUMMARY_FILE).write_text(
        json.dumps(summary, ensure_ascii=False, separators=(",", ":")),
        encoding="utf-8",
    )
    if html_out is not None:
        html_out.parent.mkdir(parents=True, exist_ok=True)
        html_out.write_text(html_page(summary), encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--root", type=Path, default=ROOT)
    parser.add_argument(
        "--html-out",
        type=Path,
        help="Optional local HTML review output. The committed contract is JSON-only.",
    )
    args = parser.parse_args()
    root = args.root.resolve()
    html_out = args.html_out.resolve() if args.html_out else None
    write_outputs(build_summary(root), root / "out", html_out=html_out)


if __name__ == "__main__":
    main()
