#!/usr/bin/env python3
"""Build script: Generate .yaml, .json, .prompt.md from persona .md files.

Supports config.yaml for dynamic variable injection and user-specific customization.
New users: copy config.example.yaml → config.yaml and customize.
"""

import json
import re
import sys
from pathlib import Path

try:
    import yaml
except ImportError:
    print("PyYAML required: pip install pyyaml")
    sys.exit(1)


def load_config(root: Path) -> dict:
    """Load config.yaml if it exists, otherwise return empty config."""
    config_path = root / "config.yaml"
    if config_path.exists():
        config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
        print(f"Config loaded: {config_path}")
        return config

    example_path = root / "config.example.yaml"
    if example_path.exists():
        print("WARN: No config.yaml found. Using defaults. Copy config.example.yaml → config.yaml to customize.")
    return {}


def flatten_config(config: dict, prefix: str = "") -> dict:
    """Flatten nested config dict for template substitution.

    Example: {"user": {"name": "Salva"}} → {"user.name": "Salva"}
    """
    flat = {}
    for key, value in config.items():
        full_key = f"{prefix}{key}" if not prefix else f"{prefix}.{key}"
        if isinstance(value, dict):
            flat.update(flatten_config(value, full_key))
        elif isinstance(value, list):
            flat[full_key] = value
            flat[f"{full_key}.count"] = len(value)
            flat[f"{full_key}.csv"] = ", ".join(str(v) for v in value if not isinstance(v, dict))
        else:
            flat[full_key] = value
    return flat


def inject_config(content: str, flat_config: dict) -> str:
    """Replace {{config.key}} placeholders with config values."""
    def replacer(match):
        key = match.group(1).strip()
        value = flat_config.get(key, match.group(0))  # keep original if not found
        if isinstance(value, list):
            return ", ".join(str(v) for v in value if not isinstance(v, dict))
        if isinstance(value, bool):
            return "enabled" if value else "disabled"
        return str(value)

    return re.sub(r"\{\{(.+?)\}\}", replacer, content)


def check_conditionals(content: str, flat_config: dict) -> str:
    """Process {{#if key}}...{{/if}} and {{#unless key}}...{{/unless}} blocks."""
    # Handle {{#if key}}content{{/if}}
    def if_replacer(match):
        key = match.group(1).strip()
        body = match.group(2)
        value = flat_config.get(key)
        if value and value not in (False, 0, "", "false", "none", "disabled", None, []):
            return body
        return ""

    content = re.sub(r"\{\{#if (.+?)\}\}(.*?)\{\{/if\}\}", if_replacer, content, flags=re.DOTALL)

    # Handle {{#unless key}}content{{/unless}}
    def unless_replacer(match):
        key = match.group(1).strip()
        body = match.group(2)
        value = flat_config.get(key)
        if not value or value in (False, 0, "", "false", "none", "disabled", None, []):
            return body
        return ""

    content = re.sub(r"\{\{#unless (.+?)\}\}(.*?)\{\{/unless\}\}", unless_replacer, content, flags=re.DOTALL)

    return content


def parse_persona_md(filepath: Path, flat_config: dict) -> dict:
    """Parse a persona markdown file into structured data."""
    content = filepath.read_text(encoding="utf-8")

    # Apply config injection
    if flat_config:
        content = check_conditionals(content, flat_config)
        content = inject_config(content, flat_config)

    # Extract YAML frontmatter
    fm_match = re.match(r"^---\n(.*?)\n---\n(.*)$", content, re.DOTALL)
    if not fm_match:
        print(f"  WARN: No frontmatter in {filepath}")
        return {}

    frontmatter = yaml.safe_load(fm_match.group(1))
    body = fm_match.group(2).strip()

    # Extract sections from body
    sections = {}
    current_section = None
    current_content = []

    for line in body.split("\n"):
        if line.startswith("## "):
            if current_section:
                sections[current_section] = "\n".join(current_content).strip()
            current_section = line[3:].strip().lower().replace(" ", "_").replace("&", "and")
            current_content = []
        else:
            current_content.append(line)

    if current_section:
        sections[current_section] = "\n".join(current_content).strip()

    return {
        "metadata": frontmatter,
        "sections": sections,
        "raw_body": body,
    }


def build_persona(persona_dir: Path, output_dir: Path, flat_config: dict, config: dict, escalation_graph: dict = None, skills_index: dict = None):
    """Build all variants for a persona directory."""
    md_files = sorted(persona_dir.glob("*.md"))
    if not md_files:
        return 0

    persona_name = persona_dir.name
    out_path = output_dir / persona_name
    out_path.mkdir(parents=True, exist_ok=True)

    # Load _meta.yaml if exists
    meta_file = persona_dir / "_meta.yaml"
    meta = {}
    if meta_file.exists():
        meta_content = meta_file.read_text(encoding="utf-8")
        if flat_config:
            meta_content = inject_config(meta_content, flat_config)
        meta = yaml.safe_load(meta_content) or {}

    # Apply config overrides for address
    addresses = config.get("persona_defaults", {}).get("custom_addresses", {})
    if persona_name in addresses:
        meta["address_to"] = addresses[persona_name]

    count = 0
    for md_file in md_files:
        if md_file.name.startswith("_"):
            continue

        variant = md_file.stem
        parsed = parse_persona_md(md_file, flat_config)
        if not parsed:
            continue

        # Build output object
        output = {**meta, **parsed["metadata"], "variant": variant, "sections": parsed["sections"]}

        # Inject config metadata
        if config:
            output["_config"] = {
                "user": config.get("user", {}).get("name", "unknown"),
                "tools": {k: v for k, v in config.get("infrastructure", {}).get("tools", {}).items() if v is True},
                "frameworks": {k: v for k, v in config.get("frameworks", {}).items() if v is True},
                "regional_focus": config.get("regional_focus", {}),
            }

        # Inject escalation graph for this persona
        if escalation_graph and persona_name in escalation_graph:
            output["escalates_to"] = escalation_graph[persona_name]

        # Inject mapped skills for this persona
        if skills_index:
            mapped_skills = []
            for skill_name, skill_info in skills_index.get("skills", {}).items():
                if persona_name in skill_info.get("personas", []):
                    mapped_skills.append(skill_name)
            if mapped_skills:
                output["skills"] = sorted(mapped_skills)

        # Inject section word counts for quality tracking
        output["_stats"] = {
            "total_words": sum(len(s.split()) for s in parsed["sections"].values()),
            "sections": list(parsed["sections"].keys()),
            "section_count": len(parsed["sections"]),
        }

        # Write YAML
        yaml_out = out_path / f"{variant}.yaml"
        yaml_out.write_text(
            yaml.dump(output, allow_unicode=True, default_flow_style=False, sort_keys=False),
            encoding="utf-8",
        )

        # Write JSON
        json_out = out_path / f"{variant}.json"
        json_out.write_text(json.dumps(output, ensure_ascii=False, indent=2), encoding="utf-8")

        # Write plain system prompt (just the body, no config metadata)
        prompt_out = out_path / f"{variant}.prompt.md"
        prompt_out.write_text(parsed["raw_body"], encoding="utf-8")

        count += 1
        print(f"  Built: {persona_name}/{variant} -> .yaml .json .prompt.md")

    return count


SKILL_PERSONA_MAP = {
    # Cybersecurity skills → personas
    "pentest": ["neo"], "nmap-recon": ["neo", "vortex"], "security-scanner": ["neo", "phantom"],
    "sql-injection-testing": ["neo", "phantom"], "stealth-browser": ["neo", "oracle"],
    "security-audit-toolkit": ["neo", "forge"], "pwnclaw-security-scan": ["neo"],
    "senior-secops": ["bastion"], "clawsec": ["neo", "vortex"],
    "pcap-analyzer": ["vortex", "bastion"], "sys-guard-linux-remediator": ["bastion"],
    "ctf-writeup-generator": ["neo"], "dns-networking": ["vortex", "architect"],
    "network-scanner": ["neo", "vortex"], "security-skill-scanner": ["neo"],
    "pentest-active-directory": ["neo"], "pentest-api-attacker": ["neo", "phantom"],
    "pentest-auth-bypass": ["neo", "phantom"], "pentest-c2-operator": ["neo", "sentinel"],
    "gov-cybersecurity": ["sentinel", "bastion"],
    # Intelligence skills → personas
    "osint-investigator": ["oracle"], "seithar-intel": ["sentinel", "frodo"],
    "freshrss": ["frodo", "oracle"], "freshrss-reader": ["frodo", "oracle"],
    "war-intel-monitor": ["frodo", "marshal"], "news-crawler": ["frodo", "herald"],
    "dellight-intelligence-ops": ["frodo", "echo"], "dellight-strategic-intelligence": ["frodo"],
    "agent-intelligence-network-scan": ["oracle"], "social-trust-manipulation-detector": ["ghost"],
    # Infrastructure skills → personas
    "docker-essentials": ["architect"], "session-logs": ["architect"],
    # Document processing → personas
    "image-ocr": ["oracle", "scribe"], "mistral-ocr": ["oracle", "scribe"],
    "pdf-text-extractor": ["scribe", "scholar"], "youtube-transcript": ["herald", "scholar"],
    # Web scraping → personas
    "deep-scraper": ["oracle"], "crawl-for-ai": ["oracle", "herald"],
}


def build_skills_index(shared_dir: Path) -> dict:
    """Index all shared skills from _shared/skills/ and _shared/paperclip-skills/."""
    index = {"skills": {}, "paperclip_skills": {}, "design_brands": [], "ui_ux_styles": 0}

    # Index shared-skills
    skills_dir = shared_dir / "skills"
    if skills_dir.exists():
        for skill_dir in sorted(skills_dir.iterdir()):
            if not skill_dir.is_dir():
                continue
            skill_md = skill_dir / "SKILL.md"
            if skill_md.exists():
                content = skill_md.read_text(encoding="utf-8")
                first_line = ""
                for line in content.split("\n"):
                    line = line.strip()
                    if line and not line.startswith(("---", "#", "name:", "description:")):
                        first_line = line[:120]
                        break
                index["skills"][skill_dir.name] = {
                    "personas": SKILL_PERSONA_MAP.get(skill_dir.name, []),
                    "summary": first_line,
                    "has_references": (skill_dir / "references").is_dir(),
                }

    # Index paperclip-skills
    pskills_dir = shared_dir / "paperclip-skills"
    if pskills_dir.exists():
        for skill_dir in sorted(pskills_dir.iterdir()):
            if not skill_dir.is_dir():
                continue
            skill_md = skill_dir / "SKILL.md"
            if skill_md.exists():
                index["paperclip_skills"][skill_dir.name] = True

    # Index design brands
    design_dir = shared_dir / "design-md"
    if design_dir.exists():
        index["design_brands"] = sorted([d.name for d in design_dir.iterdir() if d.is_dir()])

    # Count UI/UX data
    uiux_dir = shared_dir / "ui-ux-pro-max" / "data"
    if uiux_dir.exists():
        index["ui_ux_styles"] = sum(1 for f in uiux_dir.glob("*.csv"))

    return index


def build_escalation_graph(personas_dir: Path, flat_config: dict) -> dict:
    """Extract cross-persona escalation paths from Boundaries sections."""
    graph = {}  # {persona: [escalation_targets]}
    for persona_dir in sorted(personas_dir.iterdir()):
        if not persona_dir.is_dir() or persona_dir.name.startswith((".", "_")):
            continue
        general = persona_dir / "general.md"
        if not general.exists():
            continue
        parsed = parse_persona_md(general, flat_config)
        if not parsed:
            continue
        boundaries = parsed["sections"].get("boundaries", "")
        targets = re.findall(r"Escalate to \*\*(\w+)\*\*", boundaries)
        graph[persona_dir.name] = [t.lower() for t in targets]
    return graph


def build_trigger_index(personas_dir: Path) -> dict:
    """Build reverse index: trigger keyword → persona codenames for multi-agent routing."""
    index = {}  # {trigger: [persona_names]}
    for persona_dir in sorted(personas_dir.iterdir()):
        if not persona_dir.is_dir() or persona_dir.name.startswith((".", "_")):
            continue
        meta_file = persona_dir / "_meta.yaml"
        if not meta_file.exists():
            continue
        meta = yaml.safe_load(meta_file.read_text(encoding="utf-8")) or {}
        triggers = meta.get("activation_triggers", [])
        for trigger in triggers:
            t = trigger.lower()
            if t not in index:
                index[t] = []
            index[t].append(persona_dir.name)
    return index


def validate_persona(persona_name: str, parsed: dict) -> list:
    """Validate persona structure and return warnings."""
    warnings = []
    required_sections = ["soul", "expertise", "methodology", "boundaries"]
    for section in required_sections:
        if section not in parsed.get("sections", {}):
            warnings.append(f"Missing section: {section}")
        elif len(parsed["sections"][section].split()) < 30:
            warnings.append(f"Thin section ({len(parsed['sections'][section].split())} words): {section}")

    fm = parsed.get("metadata", {})
    for field in ["codename", "name", "domain", "address_to", "tone"]:
        if field not in fm:
            warnings.append(f"Missing frontmatter: {field}")

    return warnings


def build_catalog(personas_dir: Path, output_dir: Path, config: dict, flat_config: dict):
    """Generate CATALOG.md with stats, escalation paths, and trigger index."""
    addresses = config.get("persona_defaults", {}).get("custom_addresses", {})

    # Build escalation graph and trigger index
    escalation_graph = build_escalation_graph(personas_dir, flat_config)
    trigger_index = build_trigger_index(personas_dir)

    catalog_lines = [
        "# Persona Catalog\n",
        f"_Auto-generated by build.py | User: {config.get('user', {}).get('name', 'default')}_\n",
    ]

    total_words = 0
    total_sections = 0
    all_warnings = []

    for persona_dir in sorted(personas_dir.iterdir()):
        if not persona_dir.is_dir() or persona_dir.name.startswith((".", "_")):
            continue

        meta_file = persona_dir / "_meta.yaml"
        if not meta_file.exists():
            continue

        meta = yaml.safe_load(meta_file.read_text(encoding="utf-8")) or {}
        codename = meta.get("codename", persona_dir.name)
        address = addresses.get(persona_dir.name, meta.get("address_to", "N/A"))
        variants = [f.stem for f in sorted(persona_dir.glob("*.md")) if not f.name.startswith("_")]

        # Parse general.md for stats
        general = persona_dir / "general.md"
        word_count = 0
        section_count = 0
        if general.exists():
            parsed = parse_persona_md(general, flat_config)
            if parsed:
                for s in parsed["sections"].values():
                    word_count += len(s.split())
                section_count = len(parsed["sections"])
                # Validate
                warns = validate_persona(codename, parsed)
                for w in warns:
                    all_warnings.append(f"  {codename}: {w}")

        total_words += word_count
        total_sections += section_count
        escalates_to = escalation_graph.get(persona_dir.name, [])

        catalog_lines.append(f"## {codename} — {meta.get('role', 'Unknown')}")
        catalog_lines.append(f"- **Domain:** {meta.get('domain', 'N/A')}")
        catalog_lines.append(f"- **Hitap:** {address}")
        catalog_lines.append(f"- **Variants:** {', '.join(variants)}")
        catalog_lines.append(f"- **Depth:** {word_count:,} words, {section_count} sections")
        if escalates_to:
            catalog_lines.append(f"- **Escalates to:** {', '.join(escalates_to)}")
        catalog_lines.append("")

    # Add trigger index section
    catalog_lines.append("---\n")
    catalog_lines.append("## Activation Trigger Index\n")
    catalog_lines.append("_Keyword → persona routing for multi-agent systems_\n")
    for trigger in sorted(trigger_index.keys()):
        personas = ", ".join(trigger_index[trigger])
        catalog_lines.append(f"- **{trigger}** → {personas}")
    catalog_lines.append("")

    # Add stats
    catalog_lines.append("---\n")
    catalog_lines.append("## Build Statistics\n")
    catalog_lines.append(f"- Total prompt content: {total_words:,} words")
    catalog_lines.append(f"- Total sections: {total_sections}")
    catalog_lines.append(f"- Escalation connections: {sum(len(v) for v in escalation_graph.values())}")
    catalog_lines.append(f"- Unique triggers: {len(trigger_index)}")
    catalog_lines.append("")

    catalog_path = personas_dir / "CATALOG.md"
    catalog_path.write_text("\n".join(catalog_lines), encoding="utf-8")
    print(f"  Catalog: {catalog_path}")

    # Write escalation graph and trigger index as JSON for API consumers
    index_path = output_dir / "_index"
    index_path.mkdir(parents=True, exist_ok=True)

    (index_path / "escalation_graph.json").write_text(
        json.dumps(escalation_graph, indent=2, ensure_ascii=False), encoding="utf-8"
    )
    (index_path / "trigger_index.json").write_text(
        json.dumps(trigger_index, indent=2, ensure_ascii=False), encoding="utf-8"
    )
    print(f"  Index: {index_path}/escalation_graph.json, trigger_index.json")

    # Write skills index if shared dir exists
    shared_dir = personas_dir / "_shared"
    if shared_dir.exists():
        si = build_skills_index(shared_dir)
        (index_path / "skills_index.json").write_text(
            json.dumps(si, indent=2, ensure_ascii=False), encoding="utf-8"
        )
        print(f"  Skills: {len(si.get('skills', {}))} shared + {len(si.get('paperclip_skills', {}))} paperclip + {len(si.get('design_brands', []))} design brands + {si.get('ui_ux_styles', 0)} UI/UX data files")

    # Print validation warnings
    if all_warnings:
        print(f"\n  WARNINGS ({len(all_warnings)}):")
        for w in all_warnings:
            print(f"    {w}")

    return total_words


def print_summary(config: dict, total_personas: int, total_variants: int, total_words: int = 0):
    """Print build summary with config status."""
    print("\n" + "=" * 50)
    print(f"BUILD COMPLETE")
    print(f"  Personas:  {total_personas}")
    print(f"  Variants:  {total_variants}")
    print(f"  Words:     {total_words:,}")
    print(f"  Output:    generated/")
    print(f"  Index:     generated/_index/")

    if config:
        user = config.get("user", {}).get("name", "?")
        tools_on = sum(1 for v in config.get("infrastructure", {}).get("tools", {}).values() if v is True)
        frameworks_on = sum(1 for v in config.get("frameworks", {}).values() if v is True)
        regions = config.get("regional_focus", {}).get("primary", [])
        print(f"\n  Config:   {user}")
        print(f"  Tools:    {tools_on} enabled")
        print(f"  Frameworks: {frameworks_on} enabled")
        if regions:
            print(f"  Regions:  {', '.join(regions)}")
    else:
        print("\n  Config:   none (using defaults)")
        print("  Tip:      Copy config.example.yaml → config.yaml to customize")
    print("=" * 50)


def install_claude(output_dir: Path):
    """Install personas to Claude Code as slash commands (~/.claude/commands/)."""
    commands_dir = Path.home() / ".claude" / "commands"
    commands_dir.mkdir(parents=True, exist_ok=True)
    count = 0
    for persona_dir in sorted(output_dir.iterdir()):
        if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
            continue
        for prompt_file in persona_dir.glob("*.prompt.md"):
            variant = prompt_file.stem
            codename = persona_dir.name
            cmd_name = f"persona-{codename}" if variant == "general" else f"persona-{codename}-{variant}"
            dest = commands_dir / f"{cmd_name}.md"
            content = prompt_file.read_text(encoding="utf-8")
            # Wrap as Claude command: $ARGUMENTS placeholder for user query
            command_content = f"{content}\n\n---\nUser query: $ARGUMENTS\n"
            dest.write_text(command_content, encoding="utf-8")
            count += 1
    print(f"  Claude: {count} commands installed to {commands_dir}")
    return count


def install_antigravity(output_dir: Path):
    """Install personas to Antigravity IDE system prompts."""
    # Antigravity stores system prompts in ~/.config/antigravity/prompts/ or project .antigravity/
    ag_dir = Path.home() / ".config" / "antigravity" / "personas"
    ag_dir.mkdir(parents=True, exist_ok=True)
    count = 0
    for persona_dir in sorted(output_dir.iterdir()):
        if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
            continue
        for prompt_file in persona_dir.glob("*.prompt.md"):
            variant = prompt_file.stem
            codename = persona_dir.name
            dest = ag_dir / codename / f"{variant}.md"
            dest.parent.mkdir(parents=True, exist_ok=True)
            dest.write_text(prompt_file.read_text(encoding="utf-8"), encoding="utf-8")
            count += 1
    print(f"  Antigravity: {count} personas installed to {ag_dir}")
    return count


def install_gemini(output_dir: Path):
    """Install personas as Gemini Gems (JSON format for Google AI Studio)."""
    gems_dir = output_dir / "_gems"
    gems_dir.mkdir(parents=True, exist_ok=True)
    count = 0
    for persona_dir in sorted(output_dir.iterdir()):
        if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
            continue
        for json_file in persona_dir.glob("*.json"):
            data = json.loads(json_file.read_text(encoding="utf-8"))
            variant = data.get("variant", json_file.stem)
            codename = data.get("codename", persona_dir.name)
            name = data.get("name", codename.title())
            # Build Gemini Gem format
            gem = {
                "name": f"{name} — {variant}" if variant != "general" else name,
                "description": f"{data.get('role', '')} | {data.get('domain', '')}",
                "system_instruction": data.get("sections", {}).get("soul", "") + "\n\n" +
                    data.get("sections", {}).get("expertise", "") + "\n\n" +
                    data.get("sections", {}).get("methodology", "") + "\n\n" +
                    data.get("sections", {}).get("behavior_rules", ""),
                "metadata": {
                    "codename": codename,
                    "variant": variant,
                    "domain": data.get("domain", ""),
                    "address_to": data.get("address_to", ""),
                    "tone": data.get("tone", ""),
                    "activation_triggers": data.get("activation_triggers", []),
                },
            }
            dest = gems_dir / f"{codename}-{variant}.json"
            dest.write_text(json.dumps(gem, ensure_ascii=False, indent=2), encoding="utf-8")
            count += 1
    print(f"  Gemini: {count} gems generated to {gems_dir}")
    return count


def install_openclaw(output_dir: Path):
    """Install personas to OpenClaw format (IDENTITY.md + individual persona files)."""
    oc_dir = output_dir / "_openclaw"
    oc_dir.mkdir(parents=True, exist_ok=True)
    personas_dir = oc_dir / "personas"
    personas_dir.mkdir(parents=True, exist_ok=True)
    count = 0
    identity_sections = []
    for persona_dir in sorted(output_dir.iterdir()):
        if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
            continue
        general_prompt = persona_dir / "general.prompt.md"
        if not general_prompt.exists():
            continue
        content = general_prompt.read_text(encoding="utf-8")
        codename = persona_dir.name
        # Write individual persona file
        (personas_dir / f"{codename}.md").write_text(content, encoding="utf-8")
        # Extract first line as title for IDENTITY.md
        first_line = content.split("\n")[0].strip("# ").strip()
        identity_sections.append(f"### {first_line}\nSee: personas/{codename}.md\n")
        count += 1
    # Write IDENTITY.md
    identity = "# IDENTITY — Persona Definitions\n\n" + "\n".join(identity_sections)
    (oc_dir / "IDENTITY.md").write_text(identity, encoding="utf-8")
    print(f"  OpenClaw: {count} personas + IDENTITY.md to {oc_dir}")
    return count


def main():
    import argparse
    parser = argparse.ArgumentParser(description="Build persona library and optionally install to platforms.")
    parser.add_argument("--install", choices=["claude", "antigravity", "gemini", "openclaw", "all"],
                        help="Install generated personas to a target platform")
    args = parser.parse_args()

    root = Path(__file__).parent
    personas_dir = root / "personas"

    if not personas_dir.exists():
        print("No personas/ directory found.")
        sys.exit(1)

    output_dir = root / "generated"

    # Load config
    config = load_config(root)
    flat_config = flatten_config(config) if config else {}

    # Find all persona directories
    persona_dirs = [
        d for d in sorted(personas_dir.iterdir()) if d.is_dir() and not d.name.startswith((".", "_"))
    ]

    if not persona_dirs:
        print("No persona directories found.")
        sys.exit(1)

    output_dir.mkdir(parents=True, exist_ok=True)
    print(f"Building {len(persona_dirs)} personas -> {output_dir}\n")

    # Pre-build escalation graph and skills index
    escalation_graph = build_escalation_graph(personas_dir, flat_config)
    shared_dir = personas_dir / "_shared"
    skills_index = build_skills_index(shared_dir) if shared_dir.exists() else {}

    total_variants = 0
    for pdir in persona_dirs:
        total_variants += build_persona(pdir, output_dir, flat_config, config, escalation_graph, skills_index)

    total_words = build_catalog(personas_dir, output_dir, config, flat_config)

    # Platform installation
    if args.install:
        print(f"\n--- Installing to: {args.install} ---\n")
        targets = ["claude", "antigravity", "gemini", "openclaw"] if args.install == "all" else [args.install]
        for target in targets:
            if target == "claude":
                install_claude(output_dir)
            elif target == "antigravity":
                install_antigravity(output_dir)
            elif target == "gemini":
                install_gemini(output_dir)
            elif target == "openclaw":
                install_openclaw(output_dir)

    print_summary(config, len(persona_dirs), total_variants, total_words)


if __name__ == "__main__":
    main()
