#!/usr/bin/env python3
"""Verify the approved frontend design does not silently regress."""

from __future__ import annotations

import argparse
import hashlib
import json
import sys
from pathlib import Path


PROJECT_ROOT = Path(__file__).resolve().parent.parent
LOCK_PATH = Path(__file__).resolve().parent / "ui_design_lock.json"


def _sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def _load_lock() -> dict:
    return json.loads(LOCK_PATH.read_text(encoding="utf-8"))


def verify_source(lock: dict) -> list[str]:
    errors: list[str] = []
    source_files = lock.get("source_files", {})
    if not isinstance(source_files, dict):
        return ["ui_design_lock.json: source_files must be an object"]

    for rel_path, expected_hash in source_files.items():
        path = PROJECT_ROOT / rel_path
        if not path.is_file():
            errors.append(f"missing locked source file: {rel_path}")
            continue
        actual_hash = _sha256(path)
        if actual_hash != expected_hash:
            errors.append(
                f"design lock mismatch: {rel_path}\n"
                f"  expected: {expected_hash}\n"
                f"  actual:   {actual_hash}"
            )
    return errors


def verify_dist(lock: dict) -> list[str]:
    errors: list[str] = []
    dist_lock = lock.get("dist", {})
    if not isinstance(dist_lock, dict):
        return ["ui_design_lock.json: dist must be an object"]

    dist_root_rel = dist_lock.get("root")
    if not isinstance(dist_root_rel, str) or not dist_root_rel:
        return ["ui_design_lock.json: dist.root must be a non-empty string"]

    dist_root = PROJECT_ROOT / dist_root_rel
    if not dist_root.is_dir():
        return [f"missing frontend dist root: {dist_root_rel}"]

    for rel_path in dist_lock.get("required_files", []):
        candidate = dist_root / rel_path
        if not candidate.exists():
            errors.append(f"missing required dist file: {dist_root_rel}/{rel_path}")

    text_blobs: list[str] = []
    for path in dist_root.rglob("*"):
        if not path.is_file():
            continue
        if path.suffix.lower() not in {".html", ".js", ".css", ".svg", ".txt"}:
            continue
        try:
            text_blobs.append(path.read_text(encoding="utf-8", errors="ignore"))
        except Exception:
            continue
    combined_text = "\n".join(text_blobs)

    for marker in dist_lock.get("required_markers", []):
        if marker not in combined_text:
            errors.append(f"missing required dist marker: {marker}")

    return errors


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--mode",
        choices=["source", "dist", "all"],
        default="all",
        help="Which lock checks to run.",
    )
    args = parser.parse_args()

    lock = _load_lock()
    errors: list[str] = []

    if args.mode in {"source", "all"}:
        errors.extend(verify_source(lock))
    if args.mode in {"dist", "all"}:
        errors.extend(verify_dist(lock))

    if errors:
        print("[design-lock] FAILED", file=sys.stderr)
        for err in errors:
            print(f" - {err}", file=sys.stderr)
        print(
            "[design-lock] If this redesign is intentional, update ops/ui_design_lock.json explicitly.",
            file=sys.stderr,
        )
        return 1

    print(
        f"[design-lock] OK ({lock.get('design_version', 'unknown-design-version')})"
    )
    return 0


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