#!/usr/bin/env python3
"""Generate Kami Codex plugin metadata and mirror files.

Source of truth:
  - VERSION
  - root SKILL.md
  - CHEATSHEET.md
  - references/
  - scripts/
  - assets/templates/
  - assets/diagrams/
  - selected lightweight assets

Generated files:
  - .agents/plugins/marketplace.json
  - plugins/kami/.codex-plugin/plugin.json
  - plugins/kami/skills/kami/

Modes:
  --write  (default)  regenerate Codex plugin files from source
  --check             compare generated bytes against committed files

Run as: python3 scripts/build_metadata.py [--check] [--root PATH]
"""

from __future__ import annotations

import argparse
import difflib
import json
import shutil
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent

PLUGIN_NAME = "kami"
CODEX_CATEGORY = "Productivity"
HOMEPAGE = "https://github.com/tw93/kami"
REPOSITORY = "https://github.com/tw93/kami"

AUTHOR = {
    "name": "Tw93",
    "email": "hitw93@gmail.com",
    "url": "https://github.com/tw93",
}

CODEX_DESCRIPTION = (
    "Professional document and landing-page typesetting skill for Codex: "
    "resumes, one-pagers, reports, letters, portfolios, slides, and more."
)

CODEX_MIRROR_ROOT = Path("plugins/kami/skills/kami")
CODEX_MIRROR_FILES = (
    "CHEATSHEET.md",
    "LICENSE",
    "SKILL.md",
    "VERSION",
)
CODEX_MIRROR_DIRS = (
    "assets/diagrams",
    "assets/fonts",
    "assets/images",
    "assets/templates",
    "references",
    "scripts",
)
CODEX_MIRROR_ALLOWED_FONT_FILES = {
    "JetBrainsMono.woff2",
    "LICENSE-SourceHanSerifK.txt",
}
CODEX_MIRROR_IGNORED_DIRS = {
    "__pycache__",
    ".mypy_cache",
    ".pytest_cache",
    ".ruff_cache",
}
CODEX_MIRROR_IGNORED_NAMES = {
    ".DS_Store",
}
CODEX_MIRROR_IGNORED_SUFFIXES = {
    ".pyc",
    ".pyo",
}


def read_version(root: Path) -> str:
    version_file = root / "VERSION"
    if not version_file.exists():
        raise SystemExit(f"ERROR: missing VERSION file at {version_file}")
    version = version_file.read_text().strip()
    if not version:
        raise SystemExit("ERROR: VERSION file is empty")
    return version


def render_json(data: dict) -> str:
    return json.dumps(data, indent=2, ensure_ascii=False) + "\n"


def build_codex_plugin(version: str) -> dict:
    return {
        "name": PLUGIN_NAME,
        "version": version,
        "description": CODEX_DESCRIPTION,
        "author": AUTHOR,
        "homepage": HOMEPAGE,
        "repository": REPOSITORY,
        "license": "MIT",
        "keywords": [
            "codex",
            "skills",
            "documents",
            "typesetting",
            "resume",
            "slides",
            "landing-page",
        ],
        "skills": "./skills/",
        "interface": {
            "displayName": "Kami",
            "shortDescription": "Typeset polished documents and landing pages",
            "longDescription": (
                "Kami packages a warm parchment design system for Codex. Use it "
                "to turn briefs and raw material into resumes, one-pagers, long "
                "documents, letters, portfolios, slide decks, equity reports, "
                "changelogs, and product landing pages."
            ),
            "developerName": "Tw93",
            "category": CODEX_CATEGORY,
            "capabilities": [
                "Interactive",
                "Write",
            ],
            "websiteURL": HOMEPAGE,
            "defaultPrompt": [
                "Make a polished one-pager from this brief",
                "Build a resume using Kami",
                "Turn this outline into a slide deck",
            ],
            "brandColor": "#1B365D",
        },
    }


def build_codex_marketplace() -> dict:
    return {
        "name": PLUGIN_NAME,
        "interface": {
            "displayName": "Kami",
        },
        "plugins": [
            {
                "name": PLUGIN_NAME,
                "source": {
                    "source": "local",
                    "path": "./plugins/kami",
                },
                "policy": {
                    "installation": "AVAILABLE",
                    "authentication": "ON_INSTALL",
                },
                "category": CODEX_CATEGORY,
            }
        ],
    }


def should_include_codex_mirror_file(path: Path) -> bool:
    if any(part in CODEX_MIRROR_IGNORED_DIRS for part in path.parts):
        return False
    if path.name in CODEX_MIRROR_IGNORED_NAMES:
        return False
    if path.suffix in CODEX_MIRROR_IGNORED_SUFFIXES:
        return False
    if path.parts[:2] == ("assets", "fonts"):
        return path.name in CODEX_MIRROR_ALLOWED_FONT_FILES
    return True


def collect_codex_plugin_tree(root: Path, plugin_manifest_rendered: str) -> dict[str, bytes]:
    """Build the generated file set for the Codex plugin directory.

    Codex installs only the directory referenced by marketplace source.path.
    Kami's source skill lives at the repository root, so the plugin mirrors a
    lightweight skill root under plugins/kami/skills/kami and keeps all paths
    referenced by SKILL.md relative to that generated skill directory.
    """
    generated = {
        "plugins/kami/.codex-plugin/plugin.json": plugin_manifest_rendered.encode()
    }

    for source in CODEX_MIRROR_FILES:
        path = root / source
        if not path.exists():
            raise SystemExit(f"ERROR: missing required Codex plugin source file {path}")
        generated[(CODEX_MIRROR_ROOT / source).as_posix()] = path.read_bytes()

    for source in CODEX_MIRROR_DIRS:
        source_root = root / source
        if not source_root.exists():
            raise SystemExit(f"ERROR: missing required Codex plugin source tree {source_root}")
        for path in sorted(source_root.rglob("*")):
            if not path.is_file():
                continue
            source_rel = path.relative_to(root)
            if not should_include_codex_mirror_file(source_rel):
                continue
            generated[(CODEX_MIRROR_ROOT / source_rel).as_posix()] = path.read_bytes()

    return generated


def diff(label: str, expected: str, actual: str) -> str:
    return "".join(
        difflib.unified_diff(
            actual.splitlines(keepends=True),
            expected.splitlines(keepends=True),
            fromfile=f"committed:{label}",
            tofile=f"generated:{label}",
        )
    )


def bytes_diff(label: str, expected: bytes, actual: bytes) -> str:
    return diff(
        label,
        expected.decode("utf-8", errors="replace"),
        actual.decode("utf-8", errors="replace"),
    )


def check_generated(root: Path, generated_json_files: list[tuple[Path, str]], codex_plugin_tree: dict[str, bytes]) -> int:
    drift = False

    for generated_path, expected in generated_json_files:
        actual = generated_path.read_text() if generated_path.exists() else ""
        if actual != expected:
            rel = generated_path.relative_to(root).as_posix()
            print(
                f"DRIFT: {rel} is out of sync with Codex plugin metadata.\n"
                "Run scripts/build_metadata.py (no flags) to regenerate.",
            )
            print(diff(rel, expected, actual), end="")
            drift = True

    for rel, expected in codex_plugin_tree.items():
        path = root / rel
        actual = path.read_bytes() if path.exists() else b""
        if actual != expected:
            print(
                f"DRIFT: {rel} is out of sync with Kami source files.\n"
                "Run scripts/build_metadata.py (no flags) to regenerate.",
            )
            print(bytes_diff(rel, expected, actual), end="")
            drift = True

    codex_plugin_root = root / "plugins" / "kami"
    if codex_plugin_root.exists():
        expected_paths = set(codex_plugin_tree)
        for path in sorted(codex_plugin_root.rglob("*")):
            if not path.is_file():
                continue
            rel_path = path.relative_to(root)
            plugin_rel = path.relative_to(codex_plugin_root)
            if not should_include_codex_mirror_file(plugin_rel):
                continue
            rel = rel_path.as_posix()
            if rel not in expected_paths:
                print(
                    f"DRIFT: {rel} is an extra file in the generated Codex plugin tree.\n"
                    "Run scripts/build_metadata.py (no flags) to regenerate.",
                )
                drift = True

    if drift:
        return 1

    for generated_path, _ in generated_json_files:
        print(f"OK: {generated_path.relative_to(root)} matches generator")
    print("OK: plugins/kami Codex plugin tree matches generator")
    return 0


def write_generated(root: Path, generated_json_files: list[tuple[Path, str]], codex_plugin_tree: dict[str, bytes]) -> int:
    for generated_path, expected in generated_json_files:
        generated_path.parent.mkdir(parents=True, exist_ok=True)
        generated_path.write_text(expected)
        print(f"OK: wrote {generated_path.relative_to(root)} ({len(expected)} bytes)")

    codex_plugin_root = root / "plugins" / "kami"
    shutil.rmtree(codex_plugin_root, ignore_errors=True)
    for rel, expected in codex_plugin_tree.items():
        path = root / rel
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_bytes(expected)
    print(f"OK: wrote plugins/kami ({len(codex_plugin_tree)} generated files)")
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--root",
        type=Path,
        default=ROOT,
        help="Repository root (default: parent of scripts/)",
    )
    parser.add_argument(
        "--check",
        action="store_true",
        help="Compare generated bytes to committed files; exit non-zero on drift.",
    )
    args = parser.parse_args()
    root = args.root.resolve()

    version = read_version(root)
    codex_plugin_rendered = render_json(build_codex_plugin(version))
    codex_marketplace_rendered = render_json(build_codex_marketplace())
    codex_plugin_tree = collect_codex_plugin_tree(root, codex_plugin_rendered)
    generated_json_files = [
        (root / ".agents" / "plugins" / "marketplace.json", codex_marketplace_rendered),
    ]

    if args.check:
        return check_generated(root, generated_json_files, codex_plugin_tree)
    return write_generated(root, generated_json_files, codex_plugin_tree)


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