#!/usr/bin/env python3
import argparse
import json
import sys
from pathlib import Path


REQUIRED_STATES = [
    "idle",
    "sleep",
    "focus",
    "cheer",
    "lowBattery",
    "hide",
    "typing",
    "waitingApproval",
    "celebrate",
    "confused",
    "peekOut",
]


def fail(message: str) -> None:
    print(f"error: {message}", file=sys.stderr)
    raise SystemExit(1)


def main() -> None:
    parser = argparse.ArgumentParser(description="Validate a Wallive pet package manifest.")
    parser.add_argument("--package-dir", required=True)
    args = parser.parse_args()

    package_dir = Path(args.package_dir).expanduser().resolve()
    manifest_path = package_dir / "pet.json"
    if not manifest_path.exists():
        fail(f"missing manifest: {manifest_path}")

    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    if manifest.get("schemaVersion") != 2:
        fail("schemaVersion must be 2")
    if not str(manifest.get("id", "")).strip():
        fail("id must be non-empty")
    if not str(manifest.get("name", "")).strip():
        fail("name must be non-empty")

    frame = manifest.get("frame") or {}
    try:
        frame_width = int(frame["width"])
        frame_height = int(frame["height"])
    except (KeyError, TypeError, ValueError):
        fail("frame dimensions must be numeric")
    if frame_width <= 0 or frame_height <= 0:
        fail("frame dimensions must be positive")

    animations = manifest.get("animations") or {}
    for state in REQUIRED_STATES:
        animation = animations.get(state)
        if animation is None:
            fail(f"missing animation: {state}")
        try:
            image_path = str(animation["image"]).strip()
            frames = int(animation["frames"])
            fps = float(animation["fps"])
        except (KeyError, TypeError, ValueError):
            fail(f"invalid animation values: {state}")
        if not image_path:
            fail(f"animation image must be non-empty: {state}")
        if image_path.startswith("/") or "://" in image_path or ".." in Path(image_path).parts:
            fail(f"animation image must be a relative path inside the package directory: {state}")
        image_file = (package_dir / image_path).resolve()
        if package_dir not in image_file.parents and image_file != package_dir:
            fail(f"animation image escapes the package directory: {state}")
        if not image_file.exists():
            fail(f"missing animation image for {state}: {image_file}")
        if frames != 30:
            fail(f"animation frame count must be 30: {state}")
        if fps <= 0:
            fail(f"animation fps must be positive: {state}")
        if animation.get("loop") is not True:
            fail(f"animation loop must be true: {state}")

    fallback = manifest.get("fallback")
    if fallback not in animations:
        fail("fallback must reference an existing animation")

    print(f"ok: {manifest_path}")


if __name__ == "__main__":
    main()
