#!/usr/bin/env python3
"""derive_counters.py — single source of truth for repository headline counters.

Walks the canonical tree (excluding sync copies, docs site, audit workspace,
and VCS/CI internals) and derives the headline numbers that README.md,
CLAUDE.md, and .claude-plugin/marketplace.json claim:

  skills              count of SKILL.md files
  plugins_on_disk     count of **/.claude-plugin/plugin.json manifests
  plugins_registered  entries in .claude-plugin/marketplace.json `plugins` array
  python_tools        .py files in the canonical tree, excluding repo-root scripts/
                      (i.e. automation tools shipped inside skill/plugin folders)
  references          .md files under any references/ directory
  agents              .md files under any agents/ directory (excl. CLAUDE.md/README.md)
  commands            .md files under any commands/ directory (excl. CLAUDE.md/README.md)
  domains             top-level folders containing at least one SKILL.md

Modes:
  (default)   print a human-readable table
  --json      print the derived counters as JSON
  --check     exit 1 listing mismatches if the headline counters claimed in
              README.md, root CLAUDE.md ("Current Scope" line), and
              marketplace.json metadata.description disagree with derived
              values. CI gate G3.

Stdlib only. No writes ever.
"""

import argparse
import json
import re
import sys
from pathlib import Path

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

# Top-level directories excluded from the canonical tree.
EXCLUDED_TOP_LEVEL = {
    ".git",
    ".codex",
    ".codex-plugin",
    ".gemini",
    ".hermes",
    ".vibe",
    "docs",
    "audit",
    "node_modules",
    ".github",
    ".claude",
}

DOC_FILENAMES = {"CLAUDE.md", "README.md"}


def canonical_walk(root: Path):
    """Yield every file in the canonical tree (excluded top-level dirs pruned)."""
    stack = [root]
    while stack:
        current = stack.pop()
        try:
            entries = sorted(current.iterdir())
        except OSError:
            continue
        for entry in entries:
            if entry.is_symlink():
                continue
            if entry.is_dir():
                if current == root and entry.name in EXCLUDED_TOP_LEVEL:
                    continue
                stack.append(entry)
            elif entry.is_file():
                yield entry


def derive(root: Path) -> dict:
    counters = {
        "skills": 0,
        "plugins_on_disk": 0,
        "plugins_registered": 0,
        "python_tools": 0,
        "references": 0,
        "agents": 0,
        "commands": 0,
        "domains": 0,
    }
    domains = set()

    for path in canonical_walk(root):
        rel = path.relative_to(root)
        parts = rel.parts
        name = path.name

        if name == "SKILL.md":
            counters["skills"] += 1
            if len(parts) > 1:
                domains.add(parts[0])
        elif name == "plugin.json" and path.parent.name == ".claude-plugin":
            counters["plugins_on_disk"] += 1
        elif path.suffix == ".py":
            # Skill/plugin automation tools: every .py except repo-root scripts/.
            if parts[0] != "scripts":
                counters["python_tools"] += 1
        elif path.suffix == ".md":
            if "references" in parts[:-1]:
                counters["references"] += 1
            elif "agents" in parts[:-1] and name not in DOC_FILENAMES:
                counters["agents"] += 1
            elif "commands" in parts[:-1] and name not in DOC_FILENAMES:
                counters["commands"] += 1

    counters["domains"] = len(domains)
    counters["_domain_list"] = sorted(domains)

    marketplace = root / ".claude-plugin" / "marketplace.json"
    if marketplace.is_file():
        try:
            data = json.loads(marketplace.read_text(encoding="utf-8"))
            counters["plugins_registered"] = len(data.get("plugins", []))
        except (json.JSONDecodeError, OSError):
            counters["plugins_registered"] = -1

    return counters


# ---------------------------------------------------------------------------
# --check: parse the standardized claim patterns in the three headline files.
#
# Standardized claim patterns (keep docs in lockstep with these):
#   "<N> production-ready skills" (or "production-ready Claude Code skills")
#   "skills across <D> domains"
#   "<T> Python automation tools" or "<T> Python tools"
#   "<R> reference guides"
#   "<P> marketplace plugins"
# ---------------------------------------------------------------------------

CLAIM_PATTERNS = {
    "skills": re.compile(r"(\d+)\s+production-ready (?:Claude Code )?skills"),
    "domains": re.compile(r"skills across\s+(\d+)\s+domains"),
    "python_tools": re.compile(r"(\d+)\s+Python (?:automation )?tools"),
    "references": re.compile(r"(\d+)\s+reference guides"),
    "plugins_registered": re.compile(r"(\d+)\s+marketplace plugins"),
}


def extract_claims(text: str) -> dict:
    """Return {counter_name: first claimed int} for every pattern found in text."""
    claims = {}
    for key, pattern in CLAIM_PATTERNS.items():
        match = pattern.search(text)
        if match:
            claims[key] = int(match.group(1))
    return claims


def run_check(root: Path, derived: dict) -> int:
    sources = []

    readme = root / "README.md"
    if readme.is_file():
        sources.append(("README.md", readme.read_text(encoding="utf-8")))

    claude_md = root / "CLAUDE.md"
    if claude_md.is_file():
        text = claude_md.read_text(encoding="utf-8")
        # Restrict to the "Current Scope" line so history sections don't trip the gate.
        scope_lines = [ln for ln in text.splitlines() if ln.startswith("**Current Scope:**")]
        sources.append(("CLAUDE.md (Current Scope line)", "\n".join(scope_lines)))

    marketplace = root / ".claude-plugin" / "marketplace.json"
    if marketplace.is_file():
        try:
            data = json.loads(marketplace.read_text(encoding="utf-8"))
            desc = data.get("metadata", {}).get("description", "")
            sources.append(("marketplace.json metadata.description", desc))
        except (json.JSONDecodeError, OSError) as exc:
            print(f"FAIL: cannot parse marketplace.json: {exc}")
            return 1

    mismatches = []
    for label, text in sources:
        claims = extract_claims(text)
        if not claims:
            mismatches.append(f"{label}: no recognizable counter claims found")
            continue
        for key, claimed in claims.items():
            actual = derived[key]
            if claimed != actual:
                mismatches.append(
                    f"{label}: claims {key}={claimed}, derived {key}={actual}"
                )

    if mismatches:
        print("COUNTER CHECK FAILED — headline claims disagree with derived values:")
        for line in mismatches:
            print(f"  - {line}")
        print("\nRun `python3 scripts/derive_counters.py` for the ground-truth table.")
        return 1

    print("Counter check passed: README.md, CLAUDE.md, marketplace.json match derived values.")
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Derive repository headline counters from the canonical tree."
    )
    parser.add_argument("--json", action="store_true", help="emit counters as JSON")
    parser.add_argument(
        "--check",
        action="store_true",
        help="exit 1 if README.md / CLAUDE.md / marketplace.json claims drift from derived values",
    )
    args = parser.parse_args()

    derived = derive(REPO_ROOT)
    domain_list = derived.pop("_domain_list")

    if args.json:
        out = dict(derived)
        out["domain_list"] = domain_list
        print(json.dumps(out, indent=2))
    else:
        width = max(len(k) for k in derived)
        print("Derived repository counters (canonical tree)")
        print("-" * 46)
        for key, value in derived.items():
            print(f"{key.ljust(width)}  {value}")
        print("-" * 46)
        print("domains: " + ", ".join(domain_list))

    if args.check:
        derived["_domain_list"] = domain_list
        return run_check(REPO_ROOT, derived)
    return 0


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