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

ROOT = Path(sys.argv[1])
ART = ROOT / "artifacts"
EXPECTED = {
    "character_sheet.json": "ccd20cb62dc781935027eee40757d7e6131fc62e5854c14a78daa5a9d454aa9b",
    "story_architecture.json": "935d7ba397ce18c9dc11598b8935e1526200850df5599ebfd6e9c92072073ff3",
    "visual_contract.json": "9c83bd87569c1632a59a1879ed7abebfe57f0d5a011c6f17f93f5fdea988321d",
}

def sha(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()

def words(text):
    return re.findall(r"[^\W\d_]+(?:[-'][^\W\d_]+)*", text, re.UNICODE)

errors = []
for name, expected in EXPECTED.items():
    path = ART / name
    if not path.exists() or sha(path) != expected:
        errors.append(f"authority hash mismatch: {name}")

story_path = ART / "story.md"
prompts_path = ART / "visual_prompts.json"
if not story_path.exists():
    errors.append("missing artifacts/story.md")
if not prompts_path.exists():
    errors.append("missing artifacts/visual_prompts.json")

part_texts = []
if story_path.exists():
    text = story_path.read_text(encoding="utf-8")
    matches = list(re.finditer(r"(?m)^<!-- PART (\d{1,2}) -->\s*$", text))
    numbers = [int(m.group(1)) for m in matches]
    if numbers != list(range(1, 13)):
        errors.append(f"part markers invalid: {numbers}")
    else:
        for i, match in enumerate(matches):
            end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
            part_texts.append(text[match.end():end].strip())
        total = sum(len(words(part)) for part in part_texts)
        if not 14000 <= total <= 15500:
            errors.append(f"narration word count out of range: {total}")
        if re.search(r"(?im)^\s*(chương|phần)\s+\d+", text):
            errors.append("spoken heading detected")

cast = json.loads((ART / "character_sheet.json").read_text(encoding="utf-8"))["characters"]
cast_names = [item["name"] for item in cast]
if len(cast_names) != 8 or len(set(cast_names)) != 8:
    errors.append("cast authority is not exactly 8 unique names")

jobs = []
if prompts_path.exists():
    try:
        raw = json.loads(prompts_path.read_text(encoding="utf-8"))
        if isinstance(raw, list) and len(raw) == 12 and all(isinstance(x, dict) and "scenes" in x for x in raw):
            for group in raw:
                for scene in group["scenes"]:
                    jobs.append((int(group["part"]), int(scene["scene"]), scene["prompt"]))
        elif isinstance(raw, list):
            jobs = [(int(x["part"]), int(x["scene"]), x["prompt"]) for x in raw]
        expected_jobs = [(p, s) for p in range(1, 13) for s in range(1, 9)]
        if [(p, s) for p, s, _ in jobs] != expected_jobs:
            errors.append("visual prompt mapping is not canonical 12x8")
        required = [
            "strict hand-drawn 2D Chinese manhua/webtoon",
            "crisp uniform black ink outlines",
            "flat cel colors",
            "one hard-edged shadow",
            "graphic outlined 2D backgrounds",
        ]
        banned = ["no text", "no pseudo-text", "no logo", "no watermark", "no photorealism", "no 3D", "no CGI"]
        for p, s, prompt in jobs:
            missing = [token for token in required + banned if token.lower() not in prompt.lower()]
            if missing:
                errors.append(f"prompt {p:02d}.{s:02d} missing contract tokens: {missing}")
    except Exception as exc:
        errors.append(f"visual prompt parse error: {exc}")

# Audience-level reveal guard. These phrases are deliberately conservative and require manual review on any hit.
reveal_terms = {
    9: ["nguồn vốn bí mật", "quyền kiểm soát tài chính", "dòng tiền do Minh Khang"],
    10: ["người thừa kế hợp pháp", "huyết thống của Minh Khang"],
    11: ["nội dung di chúc gốc", "đánh cắp di chúc", "tráo di chúc"],
}
for threshold, terms in reveal_terms.items():
    for index, part in enumerate(part_texts[: threshold - 1], 1):
        for term in terms:
            if term.casefold() in part.casefold():
                errors.append(f"reserved reveal '{term}' appears in part {index}, before part {threshold}")

report = {
    "passed": not errors,
    "errors": errors,
    "parts": len(part_texts),
    "narration_words": sum(len(words(part)) for part in part_texts),
    "prompts": len(jobs),
    "authority_hashes_verified": not any("authority hash" in e for e in errors),
    "provider_spend_allowed": not errors,
}
print(json.dumps(report, ensure_ascii=False, indent=2))
sys.exit(0 if report["passed"] else 1)
