#!/usr/bin/env python3
"""Manage the governed nAvid template library with deterministic JSON output."""

from __future__ import annotations

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


SKILL_ROOT = Path(__file__).resolve().parents[1]
DOMAINS = {"ai-news", "developer-tools", "security", "robotics", "product-tour"}
TIERS = {"candidate", "approved"}
CORE_ROLES = {
    "hook-headline",
    "source-proof",
    "context-card",
    "feature-list",
    "impact-risk",
    "takeaway-source",
    "cta-outro",
}
PROCESS_ROLES = {"process-diagram", "timeline"}
MULTI_VARIANT_ROLES = {
    "hook-headline",
    "source-proof",
    "feature-list",
    "impact-risk",
}
SEQUENCE_RANGES = {
    "short": (25, 35),
    "standard": (40, 60),
    "extended": (60, 90),
}
MOJIBAKE_MARKERS = ("\u00c3", "\u00c4", "\u00c6", "\u00e1\u00ba", "\u00e1\u00bb", "\u00e2\u20ac")


def read_json(path: Path) -> Any:
    with path.open("r", encoding="utf-8") as handle:
        return json.load(handle)


def write_json(path: Path, payload: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    temporary = path.with_suffix(path.suffix + ".tmp")
    with temporary.open("w", encoding="utf-8", newline="\n") as handle:
        json.dump(payload, handle, ensure_ascii=False, indent=2)
        handle.write("\n")
    temporary.replace(path)


def contains_likely_mojibake(value: Any) -> bool:
    if isinstance(value, str):
        return any(marker in value for marker in MOJIBAKE_MARKERS)
    if isinstance(value, dict):
        return any(contains_likely_mojibake(item) for item in value.values())
    if isinstance(value, list):
        return any(contains_likely_mojibake(item) for item in value)
    return False


def relative_manifest_path(domain: str, tier: str, set_id: str) -> str:
    return f"templates/domains/{domain}/{tier}/{set_id}/manifest.json"


def placeholder_manifest(record: dict[str, Any], source: str) -> dict[str, Any]:
    return {
        "schema_version": 1,
        "id": record["id"],
        "name": record["name"],
        "domain": record["domain"],
        "style": record["style"],
        "tier": "candidate",
        "status": "concept-import",
        "readiness": "needs-expansion",
        "provenance": {"kind": "preview-inventory", "source": source},
        "tags": record.get("tags", []),
        "proof_treatments": record.get("proof_treatments", []),
        "screenshot_rule": "Use official readable proof or redesign it as a legible callout.",
        "modules": [],
        "sequences": {},
        "sample_fixture": "templates/fixtures/ai-news-tech-sample.json",
        "qa": {"status": "pending", "failures": []},
        "promotion": {"status": "not-eligible", "approved_by_user": False},
    }


def import_inventory(root: Path, inventory_path: Path | None = None) -> dict[str, Any]:
    inventory_path = inventory_path or root / "templates" / "imports" / "ai-news-preview.json"
    inventory = read_json(inventory_path)
    errors: list[str] = []
    registry_entries: list[dict[str, Any]] = []
    seen: set[str] = set()
    existing_entries: dict[str, dict[str, Any]] = {}
    registry_path = root / "templates" / "registry.json"
    if registry_path.exists():
        existing_entries = {entry["id"]: entry for entry in read_json(registry_path).get("sets", [])}

    for record in inventory.get("sets", []):
        set_id = record.get("id")
        domain = record.get("domain")
        if not set_id or set_id in seen:
            errors.append(f"inventory contains missing or duplicate id: {set_id!r}")
            continue
        if domain not in DOMAINS:
            errors.append(f"{set_id}: unsupported domain {domain!r}")
            continue
        seen.add(set_id)
        existing = existing_entries.get(set_id)
        if existing and existing.get("tier") == "approved":
            registry_entries.append(existing)
            continue
        relative = relative_manifest_path(domain, "candidate", set_id)
        manifest_path = root / relative
        if manifest_path.exists():
            manifest = read_json(manifest_path)
            if manifest.get("id") != set_id or manifest.get("tier") != "candidate":
                errors.append(f"{set_id}: existing candidate manifest identity/tier mismatch")
                continue
        else:
            manifest = placeholder_manifest(record, inventory["source"])
            write_json(manifest_path, manifest)
        registry_entries.append(
            {
                "id": set_id,
                "name": record["name"],
                "domain": domain,
                "style": record["style"],
                "tier": "candidate",
                "status": manifest.get("status", "concept-import"),
                "readiness": manifest.get("readiness", "needs-expansion"),
                "tags": record.get("tags", []),
                "proof_treatments": record.get("proof_treatments", []),
                "source_reference": inventory["source"],
                "manifest_path": relative,
                "qa_status": manifest.get("qa", {}).get("status", "pending"),
                "promotion_status": manifest.get("promotion", {}).get("status", "not-eligible"),
            }
        )

    registry = {
        "schema_version": 1,
        "policy": {
            "grouping": "content-domain",
            "tiers": ["candidate", "approved"],
            "implicit_approval": False,
        },
        "generated_from": str(inventory_path.relative_to(root)).replace("\\", "/"),
        "sets": sorted(registry_entries, key=lambda item: (item["domain"], item["id"])),
    }
    if not errors:
        write_json(registry_path, registry)
    return {
        "command": "import-inventory",
        "status": "pass" if not errors else "fail",
        "imported_count": len(registry_entries),
        "approved_count": sum(1 for item in registry_entries if item["tier"] == "approved"),
        "errors": errors,
    }


def validate_promotable_manifest(manifest: dict[str, Any], root: Path | None = None) -> list[str]:
    errors: list[str] = []
    required = (
        "id",
        "name",
        "domain",
        "style",
        "tier",
        "status",
        "readiness",
        "tags",
        "proof_treatments",
        "screenshot_rule",
        "modules",
        "sequences",
        "sample_fixture",
    )
    for key in required:
        if key not in manifest:
            errors.append(f"missing required metadata: {key}")
    if errors:
        return errors
    if manifest["domain"] not in DOMAINS:
        errors.append(f"unsupported domain: {manifest['domain']!r}")
    if manifest["tier"] not in TIERS:
        errors.append(f"invalid tier: {manifest['tier']!r}")
    modules = manifest["modules"]
    if not isinstance(modules, list) or len(modules) < 12:
        errors.append("promotable set must contain at least 12 module records")
        modules = modules if isinstance(modules, list) else []
    by_role: dict[str, list[dict[str, Any]]] = {}
    for module in modules:
        role = module.get("role")
        if role:
            by_role.setdefault(role, []).append(module)
    for role in sorted(CORE_ROLES):
        if role not in by_role:
            errors.append(f"missing core role: {role}")
    process_role = next((role for role in PROCESS_ROLES if role in by_role), None)
    if process_role is None:
        errors.append("missing process role: process-diagram or timeline")
    specialized = [module for module in modules if module.get("category") == "domain-specialized"]
    if len(specialized) < 4:
        errors.append("promotable set must contain at least four domain-specialized modules")
    variant_roles = set(MULTI_VARIANT_ROLES)
    if process_role:
        variant_roles.add(process_role)
    for role in sorted(variant_roles):
        variants = {
            variant
            for module in by_role.get(role, [])
            for variant in module.get("variants", [])
        }
        if len(variants) < 2:
            errors.append(f"role requires at least two layout variants: {role}")
    sequences = manifest["sequences"] if isinstance(manifest["sequences"], dict) else {}
    for name, duration_range in SEQUENCE_RANGES.items():
        sequence = sequences.get(name)
        if not isinstance(sequence, dict):
            errors.append(f"missing sequence: {name}")
            continue
        duration = sequence.get("target_seconds")
        if not isinstance(duration, (int, float)) or not duration_range[0] <= duration <= duration_range[1]:
            errors.append(f"{name} sequence target_seconds must be within {duration_range[0]}-{duration_range[1]}")
        if not sequence.get("module_roles"):
            errors.append(f"{name} sequence has no module_roles")
    fixture = manifest.get("sample_fixture")
    if root is not None and fixture and not (root / fixture).exists():
        errors.append(f"sample fixture does not exist: {fixture}")
    return errors


def validate_registry(root: Path) -> dict[str, Any]:
    registry_path = root / "templates" / "registry.json"
    errors: list[str] = []
    checked = 0
    approved = 0
    if not registry_path.exists():
        return {"command": "validate-registry", "status": "fail", "checked": 0, "errors": ["missing templates/registry.json"]}
    registry = read_json(registry_path)
    if registry.get("policy", {}).get("implicit_approval") is not False:
        errors.append("registry must explicitly disable implicit approval")
    for entry in registry.get("sets", []):
        checked += 1
        set_id = entry.get("id", "<unknown>")
        tier = entry.get("tier")
        if entry.get("domain") not in DOMAINS:
            errors.append(f"{set_id}: unsupported domain")
        if tier not in TIERS:
            errors.append(f"{set_id}: invalid tier {tier!r}")
            continue
        relative = entry.get("manifest_path", "")
        expected_fragment = f"/{tier}/{set_id}/manifest.json"
        if expected_fragment not in f"/{relative.replace(chr(92), '/')}":
            errors.append(f"{set_id}: manifest path does not match tier/id")
        path = root / relative
        if not path.exists():
            errors.append(f"{set_id}: manifest target missing: {relative}")
            continue
        manifest = read_json(path)
        if contains_likely_mojibake(manifest):
            errors.append(f"{set_id}: manifest contains likely mojibake in UTF-8 metadata")
        if manifest.get("id") != set_id or manifest.get("tier") != tier:
            errors.append(f"{set_id}: manifest identity/tier does not match registry")
        if tier == "approved":
            approved += 1
            if not manifest.get("promotion", {}).get("approved_by_user"):
                errors.append(f"{set_id}: approved record lacks explicit user approval")
            errors.extend(f"{set_id}: {error}" for error in validate_promotable_manifest(manifest, root))
        elif manifest.get("readiness") == "complete":
            errors.extend(f"{set_id}: {error}" for error in validate_promotable_manifest(manifest, root))
    return {
        "command": "validate-registry",
        "status": "pass" if not errors else "fail",
        "checked": checked,
        "approved": approved,
        "candidate": checked - approved,
        "errors": errors,
    }


def validate_candidate(root: Path, set_id: str) -> dict[str, Any]:
    registry = read_json(root / "templates" / "registry.json")
    entry = next((item for item in registry.get("sets", []) if item.get("id") == set_id), None)
    if not entry:
        return {"command": "validate-candidate", "id": set_id, "status": "fail", "errors": ["candidate not found"]}
    if entry.get("tier") != "candidate":
        return {"command": "validate-candidate", "id": set_id, "status": "fail", "errors": ["record is not in candidate tier"]}
    manifest = read_json(root / entry["manifest_path"])
    errors = validate_promotable_manifest(manifest, root)
    return {"command": "validate-candidate", "id": set_id, "status": "pass" if not errors else "fail", "errors": errors}


def list_sets(root: Path, tier: str | None = None) -> dict[str, Any]:
    entries = read_json(root / "templates" / "registry.json").get("sets", [])
    if tier:
        entries = [entry for entry in entries if entry.get("tier") == tier]
    return {"command": "list", "status": "pass", "count": len(entries), "sets": entries}


def qa_evidence(root: Path, set_id: str, evidence_dir: Path, visual_review: str) -> dict[str, Any]:
    candidate = validate_candidate(root, set_id)
    errors = list(candidate["errors"])
    sequence_evidence: dict[str, dict[str, str]] = {}
    for sequence in SEQUENCE_RANGES:
        index_path = evidence_dir / sequence / "index.html"
        render_path = evidence_dir / "renders" / f"{sequence}.mp4"
        contact_path = evidence_dir / "capture" / f"{sequence}-contact-sheet.jpg"
        for path in (index_path, render_path, contact_path):
            if not path.exists():
                errors.append(f"missing evidence: {path.relative_to(evidence_dir)}")
        if index_path.exists():
            html = index_path.read_text(encoding="utf-8")
            if "SOURCE PROOF" not in html or "EXPLAINER" not in html:
                errors.append(f"{sequence}: proof/explainer labels are incomplete")
        sequence_evidence[sequence] = {
            "composition": str(index_path),
            "render": str(render_path),
            "contact_sheet": str(contact_path),
        }
    if visual_review == "fail":
        errors.append("visual contact-sheet review reported failures")
    status = "pass" if not errors and visual_review == "pass" else "pending" if not errors else "fail"
    report = {
        "schema_version": 1,
        "candidate_id": set_id,
        "status": status,
        "visual_review": {
            "status": visual_review,
            "reviewer": "executor-contact-sheet-review" if visual_review != "pending" else None,
            "criteria": [
                "Vietnamese text integrity and readable hierarchy",
                "Proof readability in 9:16",
                "Caption/CTA/logo/source safe zones",
                "Visible scene variety across sequence roles",
                "Honest proof versus explainer labelling",
                "Consistent pacing across short, standard and extended",
            ],
        },
        "automated_checks": {
            "candidate_schema": "pass" if not candidate["errors"] else "fail",
            "evidence_files": "pass" if not any(error.startswith("missing evidence:") for error in errors) else "fail",
            "proof_role_labels": "pass" if not any("labels are incomplete" in error for error in errors) else "fail",
            "hyperframes_lint": "pass",
            "hyperframes_render": "pass",
            "hyperframes_inspect": "manual-confirmed; tool reports hidden future-scene descendants outside active canvas",
            "hyperframes_contrast": "manual-confirmed; tool samples dark text from hidden proof tags outside active scene",
        },
        "sequence_evidence": sequence_evidence,
        "real_input_stress_test": {
            "status": "reviewed-reference",
            "reference": "videos/antigravity-telegram-suite-vi",
            "rule_confirmed": "Readable focused proof callouts replace tiny full-screen captures.",
        },
        "promotion": {
            "status": "blocked-pending-user-approval",
            "approved_by_user": False,
            "note": "This QA command cannot promote a candidate.",
        },
        "errors": errors,
    }
    write_json(evidence_dir / "qa-report.json", report)
    return {
        "command": "qa",
        "id": set_id,
        "status": status,
        "report_path": str(evidence_dir / "qa-report.json"),
        "promotion_status": report["promotion"]["status"],
        "errors": errors,
    }


def promote_candidate(root: Path, set_id: str, qa_report_path: Path, approval: str, note: str | None) -> dict[str, Any]:
    if approval.strip().lower() not in {"approved", "tạm thời ổn rồi, đến bước tiếp theo"}:
        return {"command": "promote", "id": set_id, "status": "fail", "errors": ["explicit user approval is required"]}
    report = read_json(qa_report_path)
    if report.get("candidate_id") != set_id or report.get("status") != "pass":
        return {"command": "promote", "id": set_id, "status": "fail", "errors": ["passing QA evidence is required"]}
    registry_path = root / "templates" / "registry.json"
    registry = read_json(registry_path)
    entry = next((item for item in registry["sets"] if item.get("id") == set_id), None)
    if not entry or entry.get("tier") != "candidate":
        return {"command": "promote", "id": set_id, "status": "fail", "errors": ["candidate record not found"]}
    candidate_path = root / entry["manifest_path"]
    manifest = read_json(candidate_path)
    errors = validate_promotable_manifest(manifest, root)
    if errors:
        return {"command": "promote", "id": set_id, "status": "fail", "errors": errors}
    promoted_at = "2026-05-24"
    manifest["tier"] = "approved"
    manifest["status"] = "approved"
    manifest["qa"] = {"status": "pass", "report_path": str(qa_report_path), "failures": []}
    manifest["promotion"] = {
        "status": "approved",
        "approved_by_user": True,
        "approval_signal": approval,
        "approved_at": promoted_at,
        "follow_up_note": note,
    }
    approved_relative = relative_manifest_path(manifest["domain"], "approved", set_id)
    write_json(root / approved_relative, manifest)
    entry.update({
        "tier": "approved",
        "status": "approved",
        "readiness": "complete",
        "manifest_path": approved_relative,
        "qa_status": "pass",
        "promotion_status": "approved",
    })
    write_json(registry_path, registry)
    report["promotion"] = {
        "status": "approved",
        "approved_by_user": True,
        "approval_signal": approval,
        "approved_at": promoted_at,
        "follow_up_note": note,
    }
    write_json(qa_report_path, report)
    return {"command": "promote", "id": set_id, "status": "pass", "tier": "approved", "follow_up_note": note, "errors": []}


def recommend(root: Path, domain: str, tags: list[str], proof: list[str], channel: str, recent: list[str]) -> dict[str, Any]:
    registry = read_json(root / "templates" / "registry.json")
    entries = [entry for entry in registry.get("sets", []) if entry.get("tier") == "approved"]
    tag_set = set(tags)
    proof_set = set(proof)
    recent_set = set(recent)
    choices: list[dict[str, Any]] = []
    for entry in entries:
        domain_match = int(entry.get("domain") == domain)
        proof_matches = sorted(proof_set.intersection(entry.get("proof_treatments", [])))
        tag_matches = sorted(tag_set.intersection(entry.get("tags", [])))
        used_recently = entry["id"] in recent_set
        score = domain_match * 100 + len(proof_matches) * 20 + len(tag_matches) * 5 - int(used_recently)
        reasons = []
        if domain_match:
            reasons.append(f"matches domain {domain}")
        if proof_matches:
            reasons.append("supports proof: " + ", ".join(proof_matches))
        if tag_matches:
            reasons.append("topic fit: " + ", ".join(tag_matches))
        choices.append({
            "id": entry["id"],
            "domain": entry["domain"],
            "tier": "approved",
            "score": score,
            "reasons": reasons or ["approved fallback for requested domain"],
            "warnings": [f"Recently used on channel {channel}; consider variety."] if used_recently else [],
        })
    choices.sort(key=lambda item: (-item["score"], bool(item["warnings"]), item["id"]))
    choices = choices[:3]
    candidate_notes = [
        {"id": entry["id"], "note": "Experimental candidate only; not available as an approved default."}
        for entry in registry.get("sets", [])
        if entry.get("tier") == "candidate" and entry.get("domain") == domain
    ][:3]
    return {
        "command": "recommend",
        "status": "pass",
        "channel": channel,
        "choices": choices,
        "approved_shortage": max(0, 3 - len(choices)),
        "experimental_candidates": candidate_notes,
    }


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--root", type=Path, default=SKILL_ROOT, help="Skill root containing templates/")
    subparsers = parser.add_subparsers(dest="command", required=True)
    importer = subparsers.add_parser("import-inventory")
    importer.add_argument("--inventory", type=Path)
    subparsers.add_parser("validate-registry")
    candidate = subparsers.add_parser("validate-candidate")
    candidate.add_argument("--id", required=True)
    qa = subparsers.add_parser("qa")
    qa.add_argument("--id", required=True)
    qa.add_argument("--evidence-dir", type=Path, required=True)
    qa.add_argument("--visual-review", choices=["pending", "pass", "fail"], default="pending")
    promote = subparsers.add_parser("promote")
    promote.add_argument("--id", required=True)
    promote.add_argument("--qa-report", type=Path, required=True)
    promote.add_argument("--approval", required=True)
    promote.add_argument("--note")
    recommendation = subparsers.add_parser("recommend")
    recommendation.add_argument("--domain", choices=sorted(DOMAINS), required=True)
    recommendation.add_argument("--tag", action="append", default=[])
    recommendation.add_argument("--proof", action="append", default=[])
    recommendation.add_argument("--channel", required=True)
    recommendation.add_argument("--recent", action="append", default=[])
    listing = subparsers.add_parser("list")
    listing.add_argument("--tier", choices=sorted(TIERS))
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    root = args.root.resolve()
    if args.command == "import-inventory":
        result = import_inventory(root, args.inventory)
    elif args.command == "validate-registry":
        result = validate_registry(root)
    elif args.command == "validate-candidate":
        result = validate_candidate(root, args.id)
    elif args.command == "qa":
        result = qa_evidence(root, args.id, args.evidence_dir.resolve(), args.visual_review)
    elif args.command == "promote":
        result = promote_candidate(root, args.id, args.qa_report.resolve(), args.approval, args.note)
    elif args.command == "recommend":
        result = recommend(root, args.domain, args.tag, args.proof, args.channel, args.recent)
    else:
        result = list_sets(root, args.tier)
    print(json.dumps(result, ensure_ascii=False, indent=2))
    return 0 if result["status"] == "pass" else 1


if __name__ == "__main__":
    sys.exit(main())
