#!/usr/bin/env python3
"""
Agent Skills reference generator for adk-fluent.

Reads manifest.json + seed.toml + source code and produces auto-generated
reference files for .claude/skills/ and .gemini/skills/.

This follows the Agent Skills specification (agentskills.io) progressive
disclosure pattern: SKILL.md files stay hand-written (procedures, judgment),
while references/ contain auto-generated knowledge extracted from the same
data sources as the codegen pipeline.

Usage:
    python scripts/skill_generator.py manifest.json seeds/seed.toml
    python scripts/skill_generator.py manifest.json seeds/seed.toml --output-dir .
"""

from __future__ import annotations

import argparse
import inspect
import re
import shutil
import sys
from collections import defaultdict
from pathlib import Path

# Reuse the same infrastructure as llms_generator.py
sys.path.insert(0, str(Path(__file__).parent))
from generator import BuilderSpec, parse_manifest, parse_seed, resolve_builder_specs

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

HEADER = """\
<!-- Auto-generated by scripts/skill_generator.py. Do not edit manually. -->
<!-- Regenerate with: just skills -->

"""

PROJECT_ROOT = Path(__file__).resolve().parent.parent
CLAUDE_SKILLS = PROJECT_ROOT / ".claude" / "skills"
GEMINI_SKILLS = PROJECT_ROOT / ".gemini" / "skills"
DISTRIBUTABLE_SKILLS = PROJECT_ROOT / "skills"
SHARED_DIR = "_shared"

# Claude-specific frontmatter keys to strip for .gemini copies
CLAUDE_ONLY_KEYS = {"context", "agent"}

# GitHub owner/repo for install instructions
GITHUB_REPO = "vamsiramakrishnan/adk-fluent"

# Project-internal frontmatter keys to strip for distributable skills
PROJECT_ONLY_KEYS = {"context", "agent", "disable-model-invocation", "allowed-tools"}


# ---------------------------------------------------------------------------
# Reference generators
# ---------------------------------------------------------------------------


def generate_deprecated_methods(specs: list[BuilderSpec]) -> str:
    """Extract deprecated method mappings from all BuilderSpecs."""
    lines = [HEADER.strip(), "", "# Deprecated Methods Reference", ""]
    lines.append("Methods that still work but should be replaced with their modern equivalents.")
    lines.append("These are defined in `seeds/seed.manual.toml` under `[builders.*.deprecated_aliases]`.")
    lines.append("")

    all_deprecated: dict[str, dict[str, str]] = {}
    for spec in specs:
        if spec.deprecated_aliases:
            for dep_name, dep_info in spec.deprecated_aliases.items():
                use = dep_info.get("use", "?")
                field = dep_info.get("field", "?")
                all_deprecated[dep_name] = {"use": use, "field": field, "builder": spec.name}

    if not all_deprecated:
        lines.append("No deprecated methods found.")
        return "\n".join(lines)

    lines.append("| Deprecated method | Use instead | Underlying field | Builder |")
    lines.append("|---|---|---|---|")
    for dep_name in sorted(all_deprecated):
        info = all_deprecated[dep_name]
        lines.append(f"| `.{dep_name}()` | `.{info['use']}()` | `{info['field']}` | {info['builder']} |")

    lines.append("")
    lines.append("## Quick grep patterns")
    lines.append("")
    lines.append("To find deprecated usage in code, search for these patterns:")
    lines.append("```")
    for dep_name in sorted(all_deprecated):
        lines.append(f".{dep_name}(")
    lines.append("```")

    return "\n".join(lines)


def generate_api_surface(specs: list[BuilderSpec]) -> str:
    """Generate organized API surface from BuilderSpecs."""
    lines = [HEADER.strip(), "", "# API Surface Reference", ""]
    lines.append("Complete builder method inventory extracted from `seeds/seed.toml` and `manifest.json`.")
    lines.append("")

    # Group extras by concern
    concerns: dict[str, list[tuple[str, str, str]]] = defaultdict(list)

    concern_map = {
        "model": "Configuration",
        "instruct": "Configuration",
        "describe": "Configuration",
        "static": "Configuration",
        "global_instruct": "Configuration",
        "generate_content_config": "Configuration",
        "writes": "Data flow",
        "reads": "Data flow",
        "returns": "Data flow",
        "accepts": "Data flow",
        "context": "Data flow",
        "produces": "Data flow",
        "consumes": "Data flow",
        "tool": "Tools",
        "tools": "Tools",
        "agent_tool": "Tools",
        "before_agent": "Callbacks",
        "after_agent": "Callbacks",
        "before_model": "Callbacks",
        "after_model": "Callbacks",
        "before_tool": "Callbacks",
        "after_tool": "Callbacks",
        "guard": "Callbacks",
        "on_model_error": "Callbacks",
        "on_tool_error": "Callbacks",
        "loop_until": "Flow control",
        "loop_while": "Flow control",
        "until": "Flow control",
        "proceed_if": "Flow control",
        "timeout": "Flow control",
        "dispatch": "Flow control",
        "isolate": "Transfer control",
        "stay": "Transfer control",
        "no_peers": "Transfer control",
        "memory": "Memory",
        "memory_auto_save": "Memory",
        "show": "Visibility",
        "hide": "Visibility",
        "transparent": "Visibility",
        "filtered": "Visibility",
        "annotated": "Visibility",
        "middleware": "Configuration",
        "inject": "Configuration",
        "use": "Configuration",
        "native": "Configuration",
        "debug": "Configuration",
        "strict": "Configuration",
        "unchecked": "Configuration",
        "prepend": "Configuration",
        "ask": "Execution",
        "ask_async": "Execution",
        "stream": "Execution",
        "events": "Execution",
        "session": "Execution",
        "map": "Execution",
        "map_async": "Execution",
        "test": "Execution",
        "mock": "Execution",
        "eval": "Execution",
        "eval_suite": "Execution",
        "explain": "Introspection",
        "validate": "Introspection",
        "clone": "Introspection",
        "with_": "Introspection",
        "to_ir": "Introspection",
        "to_mermaid": "Introspection",
        "diagnose": "Introspection",
        "doctor": "Introspection",
        "data_flow": "Introspection",
        "llm_anatomy": "Introspection",
        "inspect": "Introspection",
        "to_dict": "Introspection",
        "to_yaml": "Introspection",
        "tool_schema": "Schemas",
        "callback_schema": "Schemas",
        "prompt_schema": "Schemas",
        "artifact_schema": "Schemas",
        "artifacts": "Schemas",
    }

    for spec in specs:
        for extra in spec.extras:
            name = extra.get("name", extra.get("method", "?"))
            sig = extra.get("signature", extra.get("sig", ""))
            doc = extra.get("doc", "")
            concern = concern_map.get(name, "Other")
            concerns[concern].append((name, sig, doc))

    # Deduplicate (same method on multiple builders)
    seen: set[str] = set()
    concern_order = [
        "Configuration",
        "Data flow",
        "Tools",
        "Callbacks",
        "Flow control",
        "Transfer control",
        "Memory",
        "Visibility",
        "Schemas",
        "Execution",
        "Introspection",
        "Other",
    ]

    for concern in concern_order:
        methods = concerns.get(concern, [])
        if not methods:
            continue
        lines.append(f"## {concern}")
        lines.append("")
        lines.append("| Method | Signature | Description |")
        lines.append("|---|---|---|")
        for name, sig, doc in sorted(methods, key=lambda x: x[0]):
            if name in seen:
                continue
            seen.add(name)
            # Clean up signature for display
            sig_short = sig.replace("(self, ", "(").replace("(self)", "()")
            doc_short = doc.split("\n")[0][:80] if doc else ""
            lines.append(f"| `.{name}()` | `{sig_short}` | {doc_short} |")
        lines.append("")

    return "\n".join(lines)


def generate_generated_files() -> str:
    """Parse .gitattributes and classify files as generated vs hand-written."""
    lines = [HEADER.strip(), "", "# Generated vs Hand-Written Files", ""]
    lines.append("Source of truth: `.gitattributes` marks all generated files.")
    lines.append("")

    gitattributes = PROJECT_ROOT / ".gitattributes"
    generated: list[str] = []
    if gitattributes.exists():
        for line in gitattributes.read_text().splitlines():
            line = line.strip()
            if line and not line.startswith("#") and "linguist-generated=true" in line:
                path = line.split()[0]
                generated.append(path)

    lines.append("## Generated files (NEVER edit directly)")
    lines.append("")
    lines.append("These are owned by the codegen pipeline (`just generate`). Changes go through")
    lines.append("`seeds/seed.manual.toml` or the generator scripts.")
    lines.append("")
    for path in sorted(generated):
        lines.append(f"- `{path}`")

    # Hand-written core files
    lines.append("")
    lines.append("## Hand-written core files (safe to edit)")
    lines.append("")

    hand_written = [
        "_base.py",
        "_context.py",
        "_context_providers.py",
        "_prompt.py",
        "_transforms.py",
        "_routing.py",
        "_primitives.py",
        "_middleware.py",
        "_tools.py",
        "_guards.py",
        "_artifacts.py",
        "_eval.py",
        "_visibility.py",
        "patterns.py",
        "middleware.py",
        "decorators.py",
        "viz.py",
        "di.py",
        "prelude.py",
        "cli.py",
    ]
    for f in hand_written:
        lines.append(f"- `src/adk_fluent/{f}`")

    lines.append("")
    lines.append("## Testing files")
    lines.append("")
    lines.append("- `tests/generated/` — Auto-generated test scaffolds (NEVER edit)")
    lines.append("- `tests/manual/` — Hand-written tests (this is where you write)")
    lines.append("- `tests/golden/` — Snapshot tests for stability")
    lines.append("- `examples/cookbook/` — Runnable examples (hand-written)")

    return "\n".join(lines)


def generate_builder_inventory(specs: list[BuilderSpec]) -> str:
    """Group builders by module with counts."""
    lines = [HEADER.strip(), "", "# Builder Inventory", ""]
    lines.append(f"**{len(specs)} builders** across the following modules:")
    lines.append("")

    groups: dict[str, list[BuilderSpec]] = defaultdict(list)
    for spec in specs:
        groups[spec.output_module].append(spec)

    for module in sorted(groups):
        builders = groups[module]
        lines.append(f"## {module} module ({len(builders)} builders)")
        lines.append("")
        for spec in sorted(builders, key=lambda s: s.name):
            extra_count = len(spec.extras)
            field_count = len([f for f in spec.fields if f["name"] not in spec.skip_fields])
            lines.append(f"- **{spec.name}** — `{spec.source_class}` ({field_count} fields, {extra_count} extras)")
        lines.append("")

    return "\n".join(lines)


def generate_patterns() -> str:
    """Extract composition pattern signatures from patterns.py."""
    lines = [HEADER.strip(), "", "# Composition Patterns & Primitives", ""]
    lines.append("Extracted from `src/adk_fluent/patterns.py` and `src/adk_fluent/_primitives.py`.")
    lines.append("")

    # Patterns
    lines.append("## Higher-order patterns (`from adk_fluent.patterns import ...`)")
    lines.append("")

    try:
        sys.path.insert(0, str(PROJECT_ROOT / "src"))
        from adk_fluent import patterns

        for name in sorted(dir(patterns)):
            obj = getattr(patterns, name)
            if callable(obj) and not name.startswith("_"):
                try:
                    sig = inspect.signature(obj)
                    doc = (inspect.getdoc(obj) or "").split("\n")[0]
                    lines.append(f"### `{name}{sig}`")
                    lines.append("")
                    if doc:
                        lines.append(doc)
                        lines.append("")
                except (ValueError, TypeError):
                    lines.append(f"### `{name}(...)`")
                    lines.append("")
    except ImportError:
        lines.append("*Could not import patterns module. Run `uv sync` first.*")
        lines.append("")

    # Expression primitives
    lines.append("## Expression primitives")
    lines.append("")
    lines.append("| Primitive | Usage | Description |")
    lines.append("|---|---|---|")
    primitives = [
        ("until", "(pred, max=10)", "Loop condition for `*` operator"),
        ("tap", "(fn)", "Inline transform (no LLM, zero cost)"),
        ("expect", "(fn)", "Alias for tap"),
        ("map_over", "(key)", "Map agent over list items in state[key]"),
        ("gate", "(predicate)", "Conditional execution (skip if false)"),
        ("race", "(*agents)", "First-to-complete wins"),
        ("dispatch", "(name=, on_complete=)", "Launch background task"),
        ("join", "()", "Wait for all background tasks"),
    ]
    for name, sig, desc in primitives:
        lines.append(f"| `{name}{sig}` | `>> {name}(...)` | {desc} |")

    lines.append("")
    lines.append("## Expression operators")
    lines.append("")
    lines.append("| Operator | Syntax | Result |")
    lines.append("|---|---|---|")
    operators = [
        (">>", "a >> b", "Pipeline (sequential)"),
        ("|", "a | b", "FanOut (parallel)"),
        ("*", "(a >> b) * 3", "Loop (fixed iterations)"),
        ("* until()", "(a >> b) * until(pred)", "Loop (conditional)"),
        ("//", "a // b", "Fallback chain"),
        ("@", "a @ Schema", "Structured output (.returns(Schema))"),
    ]
    for op, syntax, result in operators:
        lines.append(f"| `{op}` | `{syntax}` | {result} |")

    return "\n".join(lines)


def generate_namespace_methods() -> str:
    """Extract namespace module methods (S, C, P, A, M, T, E, G)."""
    lines = [HEADER.strip(), "", "# Namespace Module Methods", ""]
    lines.append("Extracted from source modules via introspection.")
    lines.append("")

    namespaces = [
        ("S", "State transforms", "_transforms", "STransform"),
        ("C", "Context engineering", "_context", "ContextSpec"),
        ("P", "Prompt composition", "_prompt", "PTransform"),
        ("A", "Artifacts", "_artifacts", "ATransform"),
        ("M", "Middleware", "_middleware", "MComposite"),
        ("T", "Tool composition", "_tools", "TComposite"),
        ("E", "Evaluation", "_eval", "EvalSuite"),
        ("G", "Guards", "_guards", "GuardViolation"),
    ]

    try:
        sys.path.insert(0, str(PROJECT_ROOT / "src"))
        import adk_fluent

        for letter, description, _module, _type in namespaces:
            cls = getattr(adk_fluent, letter, None)
            if cls is None:
                continue

            methods = []
            for name in sorted(dir(cls)):
                if name.startswith("_"):
                    continue
                obj = getattr(cls, name, None)
                if obj is None or not callable(obj):
                    continue
                try:
                    sig = inspect.signature(obj)
                    doc = (inspect.getdoc(obj) or "").split("\n")[0]
                    methods.append((name, str(sig), doc))
                except (ValueError, TypeError):
                    methods.append((name, "(...)", ""))

            lines.append(f"## {letter} — {description}")
            lines.append("")
            if methods:
                lines.append("Compose with: `+` (union/merge) or `|` (pipe/chain)")
                lines.append("")
                lines.append("| Method | Signature | Description |")
                lines.append("|---|---|---|")
                for name, sig, doc in methods:
                    sig_short = sig.replace("(self, ", "(").replace("(self)", "()")
                    doc_short = doc[:80] if doc else ""
                    lines.append(f"| `{letter}.{name}()` | `{sig_short}` | {doc_short} |")
                lines.append("")
            else:
                lines.append(f"*No methods found for {letter}.*")
                lines.append("")

    except ImportError as e:
        lines.append(f"*Could not import adk_fluent: {e}. Run `uv sync` first.*")

    return "\n".join(lines)


def generate_dev_commands() -> str:
    """Parse justfile for development commands."""
    lines = [HEADER.strip(), "", "# Development Commands", ""]
    lines.append("Extracted from `justfile`. Always use `uv run` for Python commands.")
    lines.append("")

    justfile = PROJECT_ROOT / "justfile"
    if not justfile.exists():
        lines.append("*justfile not found.*")
        return "\n".join(lines)

    text = justfile.read_text()

    # Extract target lines with comments
    lines.append("| Command | Description |")
    lines.append("|---|---|")

    # Parse the help target for clean descriptions
    in_help = False
    for line in text.splitlines():
        if line.strip().startswith("help:"):
            in_help = True
            continue
        if in_help:
            if line.strip() and not line.startswith(" ") and not line.startswith("\t") and not line.startswith("@"):
                break
            match = re.match(r'\s+@echo\s+"  (just \S+)\s+(.*)"', line)
            if match:
                cmd = match.group(1).strip()
                desc = match.group(2).strip()
                lines.append(f"| `{cmd}` | {desc} |")

    lines.append("")
    lines.append("## Common workflows")
    lines.append("")
    lines.append("```bash")
    lines.append("just setup          # First-time setup")
    lines.append("just all            # Full pipeline: scan → seed → generate → docs")
    lines.append("just ci             # Full local CI: preflight + check-gen + test")
    lines.append("just test           # Run all tests")
    lines.append("just generate       # Regenerate code from seed + manifest")
    lines.append("just skills         # Regenerate skill references")
    lines.append("```")

    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Helper script generators
# ---------------------------------------------------------------------------


def generate_check_deprecated_script() -> str:
    """Generate a script that scans code for deprecated method usage."""
    return '''\
#!/usr/bin/env python3
"""Scan files for deprecated adk-fluent method usage.

Reads deprecated aliases from seeds/seed.manual.toml and searches
target files for their usage.

Usage:
    uv run .claude/skills/_shared/scripts/check-deprecated.py [PATH...]
    uv run .claude/skills/_shared/scripts/check-deprecated.py src/ tests/ examples/
"""

from __future__ import annotations

import re
import sys
from pathlib import Path

# Find project root (where seeds/ lives)
script_dir = Path(__file__).resolve().parent
project_root = script_dir
for _ in range(10):
    if (project_root / "seeds").exists():
        break
    project_root = project_root.parent

try:
    import tomllib
except ImportError:
    import tomli as tomllib  # type: ignore[no-redef]


def load_deprecated_aliases() -> dict[str, str]:
    """Load deprecated aliases from seed.manual.toml."""
    seed_path = project_root / "seeds" / "seed.manual.toml"
    if not seed_path.exists():
        print(f"ERROR: {seed_path} not found", file=sys.stderr)
        sys.exit(1)

    with open(seed_path, "rb") as f:
        seed = tomllib.load(f)

    aliases: dict[str, str] = {}
    for _builder_name, builder_config in seed.get("builders", {}).items():
        for dep_name, dep_val in builder_config.get("deprecated_aliases", {}).items():
            if isinstance(dep_val, dict):
                aliases[dep_name] = dep_val.get("use", "?")
            else:
                aliases[dep_name] = str(dep_val)
    return aliases


def scan_file(path: Path, aliases: dict[str, str]) -> list[tuple[int, str, str, str]]:
    """Scan a file for deprecated method calls. Returns (line_no, line, deprecated, replacement)."""
    findings = []
    try:
        text = path.read_text()
    except (UnicodeDecodeError, OSError):
        return findings

    for i, line in enumerate(text.splitlines(), 1):
        for dep_name, replacement in aliases.items():
            if re.search(rf"\\.{dep_name}\\s*\\(", line):
                findings.append((i, line.strip(), dep_name, replacement))
    return findings


def main():
    paths = [Path(p) for p in sys.argv[1:]] if len(sys.argv) > 1 else [project_root / "src"]

    aliases = load_deprecated_aliases()
    if not aliases:
        print("No deprecated aliases found in seed.manual.toml")
        return

    total_findings = 0
    for root_path in paths:
        if root_path.is_file():
            files = [root_path]
        else:
            files = sorted(root_path.rglob("*.py"))

        for path in files:
            findings = scan_file(path, aliases)
            for line_no, line_text, dep_name, replacement in findings:
                rel = path.relative_to(project_root) if path.is_relative_to(project_root) else path
                print(f"{rel}:{line_no}: `.{dep_name}()` → use `.{replacement}()` instead")
                print(f"  {line_text}")
                print()
                total_findings += 1

    if total_findings == 0:
        print("No deprecated method usage found.")
    else:
        print(f"Found {total_findings} deprecated method usage(s).")


if __name__ == "__main__":
    main()
'''


def generate_list_builders_script() -> str:
    """Generate a script that prints builder inventory from manifest."""
    return '''\
#!/usr/bin/env python3
"""Print adk-fluent builder inventory from manifest.json.

Usage:
    uv run .claude/skills/_shared/scripts/list-builders.py
    uv run .claude/skills/_shared/scripts/list-builders.py --module agent
"""

from __future__ import annotations

import json
import sys
from pathlib import Path

project_root = Path(__file__).resolve().parent
for _ in range(10):
    if (project_root / "manifest.json").exists():
        break
    project_root = project_root.parent


def main():
    manifest_path = project_root / "manifest.json"
    if not manifest_path.exists():
        print(
            f"ERROR: {manifest_path} not found. Run `just scan` first.",
            file=sys.stderr,
        )
        sys.exit(1)

    with open(manifest_path) as f:
        manifest = json.load(f)

    module_filter = None
    if len(sys.argv) > 2 and sys.argv[1] == "--module":
        module_filter = sys.argv[2]

    print(f"ADK version: {manifest.get('adk_version', '?')}")
    print(f"Total classes: {manifest.get('total_classes', '?')}")
    print(f"Total fields: {manifest.get('total_fields', '?')}")
    print()

    for cls in sorted(manifest.get("classes", []), key=lambda c: c["name"]):
        name = cls["name"]
        qualname = cls.get("qualname", "?")
        field_count = len(cls.get("fields", []))
        if module_filter and module_filter not in qualname:
            continue
        print(f"  {name:30s}  {qualname:60s}  ({field_count} fields)")


if __name__ == "__main__":
    main()
'''


def generate_list_generated_files_script() -> str:
    """Generate a script that classifies files as generated vs hand-written."""
    return '''\
#!/usr/bin/env python3
"""Print generated vs hand-written file classification.

Reads .gitattributes to determine which files are auto-generated.

Usage:
    uv run .claude/skills/_shared/scripts/list-generated-files.py
"""

from __future__ import annotations

from pathlib import Path

project_root = Path(__file__).resolve().parent
for _ in range(10):
    if (project_root / ".gitattributes").exists():
        break
    project_root = project_root.parent


def main():
    gitattributes = project_root / ".gitattributes"
    if not gitattributes.exists():
        print("ERROR: .gitattributes not found.")
        return

    generated = []
    for line in gitattributes.read_text().splitlines():
        line = line.strip()
        if line and not line.startswith("#") and "linguist-generated=true" in line:
            generated.append(line.split()[0])

    print("GENERATED FILES (do not edit directly):")
    print("=" * 50)
    for path in sorted(generated):
        print(f"  {path}")

    print()
    print("HAND-WRITTEN CORE (safe to edit):")
    print("=" * 50)
    src = project_root / "src" / "adk_fluent"
    if src.exists():
        for f in sorted(src.glob("*.py")):
            rel = f.relative_to(project_root)
            is_gen = any(str(rel) == g or str(rel).replace("\\\\", "/") == g for g in generated)
            if not is_gen:
                print(f"  {rel}")


if __name__ == "__main__":
    main()
'''


def generate_validate_imports_script() -> str:
    """Generate a script that checks for internal module imports."""
    return '''\
#!/usr/bin/env python3
"""Check for internal adk_fluent module imports.

Imports should be from `adk_fluent` top-level, not internal modules.

Usage:
    uv run .claude/skills/_shared/scripts/validate-imports.py [PATH...]
    uv run .claude/skills/_shared/scripts/validate-imports.py examples/ tests/manual/
"""

from __future__ import annotations

import re
import sys
from pathlib import Path

project_root = Path(__file__).resolve().parent
for _ in range(10):
    if (project_root / "src").exists():
        break
    project_root = project_root.parent

# Internal modules that should not be imported from directly
INTERNAL_PATTERNS = [
    r"from\\s+adk_fluent\\._",  # from adk_fluent._base import ...
    r"from\\s+adk_fluent\\.agent\\s",  # from adk_fluent.agent import ...
    r"from\\s+adk_fluent\\.workflow\\s",  # from adk_fluent.workflow import ...
    r"from\\s+adk_fluent\\.tool\\s",  # from adk_fluent.tool import ...
    r"from\\s+adk_fluent\\.config\\s",  # from adk_fluent.config import ...
    r"from\\s+adk_fluent\\.runtime\\s",
    r"from\\s+adk_fluent\\.service\\s",
    r"from\\s+adk_fluent\\.plugin\\s",
    r"from\\s+adk_fluent\\.executor\\s",
    r"from\\s+adk_fluent\\.planner\\s",
]


def scan_file(path: Path) -> list[tuple[int, str]]:
    """Scan a file for internal imports."""
    findings = []
    try:
        text = path.read_text()
    except (UnicodeDecodeError, OSError):
        return findings

    for i, line in enumerate(text.splitlines(), 1):
        for pattern in INTERNAL_PATTERNS:
            if re.search(pattern, line):
                findings.append((i, line.strip()))
                break
    return findings


def main():
    paths = (
        [Path(p) for p in sys.argv[1:]]
        if len(sys.argv) > 1
        else [
            project_root / "examples",
        ]
    )

    total = 0
    for root_path in paths:
        files = [root_path] if root_path.is_file() else sorted(root_path.rglob("*.py"))
        for path in files:
            findings = scan_file(path)
            for line_no, line_text in findings:
                rel = path.relative_to(project_root) if path.is_relative_to(project_root) else path
                print(f"{rel}:{line_no}: internal import detected")
                print(f"  {line_text}")
                print("  → Use: from adk_fluent import ...")
                print()
                total += 1

    if total == 0:
        print("No internal import violations found.")
    else:
        print(f"Found {total} internal import violation(s).")
        sys.exit(1)


if __name__ == "__main__":
    main()
'''


# ---------------------------------------------------------------------------
# Sync mechanism
# ---------------------------------------------------------------------------


def _get_version() -> str:
    """Read project version from _version.py or pyproject.toml."""
    # Try _version.py first (single source of truth for hatch dynamic versioning)
    version_py = PROJECT_ROOT / "src" / "adk_fluent" / "_version.py"
    if version_py.exists():
        text = version_py.read_text()
        match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', text)
        if match:
            return match.group(1)

    # Fallback to pyproject.toml
    pyproject = PROJECT_ROOT / "pyproject.toml"
    if pyproject.exists():
        text = pyproject.read_text()
        for line in text.splitlines():
            if line.strip().startswith("version"):
                return line.split("=")[1].strip().strip('"').strip("'")
    return "0.1"


# ---------------------------------------------------------------------------
# Distributable skills generators
# ---------------------------------------------------------------------------


def _generate_distributable_skill(
    name: str,
    description: str,
    body: str,
    *,
    version: str = "0.1",
) -> str:
    """Generate a self-contained distributable SKILL.md."""
    lines = [
        "---",
        f"name: {name}",
        "description: >",
    ]
    # Wrap description at ~80 chars
    for desc_line in description.strip().splitlines():
        lines.append(f"  {desc_line.strip()}")
    lines.extend(
        [
            "metadata:",
            "  license: Apache-2.0",
            "  author: vamsiramakrishnan",
            f'  version: "{version}"',
            "---",
            "",
        ]
    )
    lines.append(body)
    return "\n".join(lines)


def generate_distributable_cheatsheet(specs: list[BuilderSpec], version: str) -> str:
    """Generate the adk-fluent-cheatsheet distributable skill."""
    # Import canonical content from llms_generator
    from llms_generator import (
        _AGENT_METHODS,
        _BEST_PRACTICES,
        _COMPOSITION_PATTERNS,
        _CORE_PATTERNS,
        _EXPRESSION_PRIMITIVES,
        _NAMESPACE_MODULES,
    )

    body_parts = [
        "# adk-fluent Cheatsheet",
        "",
        "> Quick reference for writing agents with adk-fluent.",
        "> For creating a new project, use `/adk-fluent-scaffold`.",
        "",
        "## Install & Import",
        "",
        "```bash",
        "pip install adk-fluent",
        "```",
        "",
        "```python",
        "from adk_fluent import Agent, Pipeline, FanOut, Loop, Route",
        "from adk_fluent import S, C, P, A, M, T, E, G",
        "```",
        "",
        "Never import from internal modules (`adk_fluent._base`, `adk_fluent.agent`).",
        "",
        "---",
        "",
        "## Native ADK → adk-fluent Mapping",
        "",
        "| Native ADK | adk-fluent equivalent |",
        "|------------|----------------------|",
        '| `LlmAgent(name=, model=, instruction=)` | `Agent("name", "model").instruct("...")` |',
        '| `SequentialAgent(name=, sub_agents=[a, b])` | `Pipeline("name").step(a).step(b)` or `a >> b` |',
        '| `ParallelAgent(name=, sub_agents=[a, b])` | `FanOut("name").branch(a).branch(b)` or `a \\| b` |',
        '| `LoopAgent(name=, sub_agents=, max_iterations=3)` | `Loop("name").step(a).max_iterations(3)` or `(a >> b) * 3` |',
        "| `AgentTool(agent=child)` | `.agent_tool(child)` |",
        "| `sub_agents=[child]` | `.sub_agent(child)` |",
        '| `output_key="key"` | `.writes("key")` |',
        '| `include_contents="none"` | `.reads("key")` or `.context(C.none())` |',
        "| `before_agent_callback=fn` | `.before_agent(fn)` |",
        "| `after_model_callback=fn` | `.after_model(fn)` |",
        "",
    ]

    body_parts.append(_CORE_PATTERNS.strip())
    body_parts.append("")
    body_parts.append(_AGENT_METHODS.strip())
    body_parts.append("")
    body_parts.append(_NAMESPACE_MODULES.strip())
    body_parts.append("")
    body_parts.append(_EXPRESSION_PRIMITIVES.strip())
    body_parts.append("")
    body_parts.append(_COMPOSITION_PATTERNS.strip())
    body_parts.append("")
    body_parts.append("## Execution")
    body_parts.append("")
    body_parts.append("```python")
    body_parts.append("# One-shot (sync)")
    body_parts.append('result = agent.ask("What is 2+2?")')
    body_parts.append("")
    body_parts.append("# One-shot (async — use in Jupyter/FastAPI)")
    body_parts.append('result = await agent.ask_async("What is 2+2?")')
    body_parts.append("")
    body_parts.append("# Streaming")
    body_parts.append('async for chunk in agent.stream("Tell me a story"):')
    body_parts.append('    print(chunk, end="")')
    body_parts.append("")
    body_parts.append("# Multi-turn session")
    body_parts.append("async with agent.session() as chat:")
    body_parts.append('    r1 = await chat.send("Hello")')
    body_parts.append("")
    body_parts.append("# Batch")
    body_parts.append('results = agent.map(["prompt1", "prompt2"], concurrency=5)')
    body_parts.append("")
    body_parts.append("# Testing (no API key needed)")
    body_parts.append('agent.mock(["canned response"]).test("input", contains="canned")')
    body_parts.append("```")
    body_parts.append("")
    body_parts.append("## Common Gotchas")
    body_parts.append("")
    body_parts.append("| Mistake | Fix |")
    body_parts.append("|---------|-----|")
    body_parts.append("| `.build()` on sub-builders inside Pipeline/FanOut/Loop | Sub-builders auto-build |")
    body_parts.append("| `.ask()` in async context | Use `.ask_async()` |")
    body_parts.append(
        '| Missing `.writes()` upstream of `.reads()` | Every `.reads("key")` needs `.writes("key")` upstream |'
    )
    body_parts.append("| `.instruct()` for metadata | Use `.describe()` for metadata |")
    body_parts.append("| LLM routing when rules suffice | Use `Route()` |")
    body_parts.append("| Retry logic in tools | Use `M.retry()` |")
    body_parts.append("| DB clients in tool schemas | Use `.inject(db=client)` |")
    body_parts.append("")
    body_parts.append("## Introspection & Debugging")
    body_parts.append("")
    body_parts.append("```python")
    body_parts.append("agent.explain()          # Quick text summary")
    body_parts.append("agent.llm_anatomy()      # What the LLM sees")
    body_parts.append("agent.data_flow()        # Five-concern view")
    body_parts.append("agent.doctor()           # Formatted diagnostic")
    body_parts.append("agent.validate()         # Catch config errors early")
    body_parts.append("agent.to_mermaid()       # Mermaid diagram")
    body_parts.append("```")
    body_parts.append("")
    body_parts.append(_BEST_PRACTICES.strip())

    body = "\n".join(body_parts)

    return _generate_distributable_skill(
        "adk-fluent-cheatsheet",
        "MUST READ before writing or modifying adk-fluent agent code.\n"
        "adk-fluent API quick reference — builder methods, operators, namespaces,\n"
        "patterns, and common idioms. Includes mappings from native ADK to fluent API.\n"
        "Use when writing agents with adk-fluent, looking up builder methods,\n"
        "or translating native ADK code to fluent style.\n"
        "Do NOT use for creating new projects (use adk-fluent-scaffold).",
        body,
        version=version,
    )


def generate_distributable_skills(specs: list[BuilderSpec], version: str) -> dict[str, str]:
    """Generate all distributable skills. Returns {dir_name: content}."""
    skills: dict[str, str] = {}

    # 1. Cheatsheet — generated from canonical llms content
    skills["adk-fluent-cheatsheet"] = generate_distributable_cheatsheet(specs, version)

    # 2-6. Copy from .claude/skills/ with project-specific refs stripped
    # These are hand-authored but auto-packaged for distribution
    mapping = {
        "dev-guide": "adk-fluent-dev-guide",
        "eval-agent": "adk-fluent-eval-guide",
        "deploy-agent": "adk-fluent-deploy-guide",
        "observe-agent": "adk-fluent-observe-guide",
        "scaffold-project": "adk-fluent-scaffold",
    }

    for internal_name, dist_name in mapping.items():
        skill_md = CLAUDE_SKILLS / internal_name / "SKILL.md"
        if not skill_md.exists():
            continue
        content = skill_md.read_text()

        # Transform: strip project-internal keys, fix name, add metadata
        lines = content.splitlines(keepends=True)
        result: list[str] = []
        in_frontmatter = False
        fm_count = 0
        name_replaced = False
        metadata_added = False

        for line in lines:
            stripped = line.strip()
            if stripped == "---":
                fm_count += 1
                in_frontmatter = fm_count == 1
                if fm_count == 2 and not metadata_added:
                    result.append(
                        f'metadata:\n  license: Apache-2.0\n  author: vamsiramakrishnan\n  version: "{version}"\n'
                    )
                    metadata_added = True
                result.append(line)
                continue

            if in_frontmatter:
                key = stripped.split(":")[0].strip() if ":" in stripped else ""
                # Strip project-only keys
                if key in PROJECT_ONLY_KEYS:
                    continue
                # Replace name
                if key == "name" and not name_replaced:
                    result.append(f"name: {dist_name}\n")
                    name_replaced = True
                    continue
                # Skip existing metadata (we add our own)
                if key == "metadata":
                    # skip metadata block
                    metadata_added = True
                    continue
                if stripped.startswith("license:") or stripped.startswith("author:") or stripped.startswith("version:"):
                    # Part of metadata block
                    continue

            # Strip _shared/ references (won't exist in distributable)
            line = line.replace("[`../_shared/references/", "[`")
            line = line.replace("](../_shared/references/", "](")
            result.append(line)

        skills[dist_name] = "".join(result)

    return skills


def generate_distributable_readme(skills: dict[str, str]) -> str:
    """Generate README.md for the distributable skills/ directory."""
    lines = [
        "Development skills for building agents with adk-fluent. Install into any coding agent via `npx skills`.",
        "",
        "## Install",
        "",
        "```bash",
        f"npx skills add {GITHUB_REPO} -y -g",
        "```",
        "",
        "## Update",
        "",
        "```bash",
        "npx skills check -g",
        "npx skills update -g",
        "```",
        "",
        "## Skills",
        "",
        "| Skill | Description |",
        "|-------|-------------|",
    ]

    for name in sorted(skills):
        # Extract description from frontmatter
        content = skills[name]
        desc = ""
        in_fm = False
        in_desc = False
        for line in content.splitlines():
            if line.strip() == "---":
                if in_fm:
                    break
                in_fm = True
                continue
            if in_fm:
                if line.strip().startswith("description:"):
                    desc = line.split(":", 1)[1].strip().lstrip(">").strip()
                    in_desc = True
                elif in_desc and line.startswith("  "):
                    desc += " " + line.strip()
                elif in_desc:
                    in_desc = False

        # Truncate to first sentence for table
        short = desc.split(".")[0].strip() + "." if "." in desc else desc[:80]
        lines.append(f"| `{name}` | {short} |")

    lines.extend(
        [
            "",
            "## Compatibility",
            "",
            "These skills work with any [Agent Skills](https://agentskills.io)-compatible tool:",
            "Gemini CLI, Claude Code, Cursor, GitHub Copilot, Amp, and more.",
            "",
        ]
    )

    return "\n".join(lines)


def _transform_skill_md_for_gemini(content: str) -> str:
    """Transform a Claude SKILL.md for Gemini: strip Claude-only keys, fix paths."""
    lines = content.splitlines(keepends=True)
    result: list[str] = []
    in_frontmatter = False
    frontmatter_count = 0

    for line in lines:
        stripped = line.strip()
        if stripped == "---":
            frontmatter_count += 1
            in_frontmatter = frontmatter_count == 1
            result.append(line)
            continue

        if in_frontmatter:
            # Strip Claude-specific frontmatter keys
            key = stripped.split(":")[0].strip() if ":" in stripped else ""
            if key in CLAUDE_ONLY_KEYS:
                continue

        # Replace .claude/ paths with .gemini/
        line = line.replace(".claude/skills/", ".gemini/skills/")
        result.append(line)

    return "".join(result)


def sync_to_gemini():
    """Sync _shared directory and SKILL.md files from .claude to .gemini."""
    # 1. Sync _shared/ (references + scripts)
    claude_shared = CLAUDE_SKILLS / SHARED_DIR
    gemini_shared = GEMINI_SKILLS / SHARED_DIR

    if gemini_shared.exists():
        shutil.rmtree(gemini_shared)

    shutil.copytree(claude_shared, gemini_shared)

    # 2. Sync individual SKILL.md files with transformations
    skill_count = 0
    for skill_dir in sorted(CLAUDE_SKILLS.iterdir()):
        if not skill_dir.is_dir() or skill_dir.name == SHARED_DIR:
            continue
        skill_md = skill_dir / "SKILL.md"
        if not skill_md.exists():
            continue

        content = skill_md.read_text()
        transformed = _transform_skill_md_for_gemini(content)

        gemini_skill_dir = GEMINI_SKILLS / skill_dir.name
        gemini_skill_dir.mkdir(parents=True, exist_ok=True)
        gemini_md = gemini_skill_dir / "SKILL.md"
        if not transformed.endswith("\n"):
            transformed += "\n"
        gemini_md.write_text(transformed)
        skill_count += 1

    return skill_count


# ---------------------------------------------------------------------------
# File writing
# ---------------------------------------------------------------------------


def write_file(path: Path, content: str) -> None:
    """Write file, creating parent dirs as needed."""
    path.parent.mkdir(parents=True, exist_ok=True)
    # Ensure trailing newline for POSIX compliance (end-of-file-fixer)
    if not content.endswith("\n"):
        content += "\n"
    path.write_text(content)


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------


def main():
    parser = argparse.ArgumentParser(description="Generate agent skill reference files from manifest + seed")
    parser.add_argument("manifest", help="Path to manifest.json")
    parser.add_argument("seed", help="Path to seed.toml")
    parser.add_argument(
        "--output-dir",
        default=".",
        help="Root directory for output (default: project root)",
    )
    args = parser.parse_args()

    manifest = parse_manifest(args.manifest)
    seed = parse_seed(args.seed)
    specs = resolve_builder_specs(seed, manifest)

    refs_dir = CLAUDE_SKILLS / SHARED_DIR / "references"
    scripts_dir = CLAUDE_SKILLS / SHARED_DIR / "scripts"

    # Generate reference files
    references = {
        "deprecated-methods.md": generate_deprecated_methods(specs),
        "api-surface.md": generate_api_surface(specs),
        "generated-files.md": generate_generated_files(),
        "builder-inventory.md": generate_builder_inventory(specs),
        "patterns-and-primitives.md": generate_patterns(),
        "namespace-methods.md": generate_namespace_methods(),
        "development-commands.md": generate_dev_commands(),
    }

    for filename, content in references.items():
        write_file(refs_dir / filename, content)
        print(f"  Generated references/{filename}")

    # Generate helper scripts
    scripts = {
        "check-deprecated.py": generate_check_deprecated_script(),
        "list-builders.py": generate_list_builders_script(),
        "list-generated-files.py": generate_list_generated_files_script(),
        "validate-imports.py": generate_validate_imports_script(),
    }

    for filename, content in scripts.items():
        write_file(scripts_dir / filename, content)
        (scripts_dir / filename).chmod(0o755)
        print(f"  Generated scripts/{filename}")

    # Sync to .gemini (shared refs + individual SKILL.md files)
    skill_count = sync_to_gemini()
    print(f"\n  Synced _shared/ + {skill_count} SKILL.md files to .gemini/skills/")

    # Generate distributable skills (skills/ directory for npx skills add)
    version = _get_version()
    dist_skills = generate_distributable_skills(specs, version)

    # Clean and recreate distributable skills directory
    if DISTRIBUTABLE_SKILLS.exists():
        shutil.rmtree(DISTRIBUTABLE_SKILLS)
    DISTRIBUTABLE_SKILLS.mkdir(parents=True, exist_ok=True)

    for dir_name, content in dist_skills.items():
        write_file(DISTRIBUTABLE_SKILLS / dir_name / "SKILL.md", content)
        print(f"  Generated skills/{dir_name}/SKILL.md")

    # Generate README for distributable skills
    readme_content = generate_distributable_readme(dist_skills)
    write_file(DISTRIBUTABLE_SKILLS / "README.md", readme_content)
    print("  Generated skills/README.md")

    print(
        f"\nGenerated {len(references)} references + {len(scripts)} scripts + {len(dist_skills)} distributable skills from {len(specs)} builder specs."
    )


if __name__ == "__main__":
    main()
