"""Golem MCP installer — copies plugin + skills into a Godot project."""

import json
import os
import shutil
import sys
from pathlib import Path
from typing import Callable

# ══════════════════════════════════════════════════════════════════════════════
# Agent configurations
# ══════════════════════════════════════════════════════════════════════════════

AgentConfig = dict[str, any]

AGENT_CONFIGS: dict[str, AgentConfig] = {
    "claude": {
        "name": "Claude Code",
        "path": lambda project: project / ".mcp.json",
        "key": "mcpServers",
        "global": False,
    },
    "cursor": {
        "name": "Cursor",
        "path": lambda project: project / ".cursor" / "mcp.json",
        "key": "mcpServers",
        "global": False,
    },
    "windsurf": {
        "name": "Windsurf",
        "path": lambda _: Path.home() / ".codeium" / "windsurf" / "mcp_config.json",
        "key": "mcpServers",
        "global": True,
    },
    "continue": {
        "name": "Continue.dev",
        "path": lambda project: project / ".continue" / "config.json",
        "key": "mcpServers",
        "global": False,
    },
    "cline": {
        "name": "Cline",
        "manual": True,
    },
}

AGENT_INSTRUCTIONS: dict[str, str] = {
    "claude": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Run: claude
  3. Type: /godot or /init
""",
    "cursor": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Open project in Cursor
  3. The Godot MCP will be available automatically
""",
    "windsurf": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Open project in Windsurf
  3. The Godot MCP is configured globally and ready to use
""",
    "continue": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Open project in your editor with Continue.dev
  3. The Godot MCP will be available in Continue
""",
    "cline": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Open VS Code with Cline extension
  3. Go to Cline settings > MCP Servers
  4. Add a new server with:
     - Name: godot
     - Command: uvx golem-mcp
     - Type: stdio
""",
}


def _get_version() -> str:
    try:
        from importlib.metadata import version
        return version("golem-mcp")
    except Exception:
        toml = Path(__file__).parent / "pyproject.toml"
        if toml.exists():
            for line in toml.read_text().splitlines():
                if line.strip().startswith("version"):
                    return line.split('"')[1]
        return "0.0.0"


def _get_data_dir() -> Path:
    """Locate bundled assets: installed package or dev checkout."""
    # Installed via pip/uvx — assets in golem_mcp_data/
    pkg_data = Path(__file__).parent / "golem_mcp_data"
    if (pkg_data / "godot-plugin").is_dir():
        return pkg_data

    # Dev checkout — assets at repo root
    repo = Path(__file__).parent
    if (repo / "godot-plugin").is_dir():
        return repo

    print("✗ Cannot locate Golem MCP assets (godot-plugin/ not found)")
    sys.exit(1)


def _validate_project(path: Path) -> None:
    if not path.is_dir():
        print(f"✗ Directory not found: {path}")
        sys.exit(1)
    if not (path / "project.godot").exists():
        print(f"✗ Not a Godot project (no project.godot): {path}")
        sys.exit(1)


def _copy_dir(src: Path, dst: Path) -> int:
    """Copy src directory to dst, return file count."""
    if dst.exists():
        shutil.rmtree(dst)
    shutil.copytree(src, dst)
    return sum(1 for f in dst.rglob("*") if f.is_file())


def _copy_plugin(data_dir: Path, project: Path) -> int:
    src = data_dir / "godot-plugin"
    dst = project / "addons" / "golem-mcp"
    return _copy_dir(src, dst)


def _copy_skills(data_dir: Path, project: Path) -> int:
    # In installed package: golem_mcp_data/commands/
    # In dev checkout: .claude/commands/
    src = data_dir / "commands"
    if not src.exists():
        src = data_dir / ".claude" / "commands"
    if not src.exists():
        print(f"  ⚠ Skills not found in {data_dir}")
        return 0

    dst = project / ".claude" / "commands"
    dst.mkdir(parents=True, exist_ok=True)
    count = 0
    for md in src.glob("*.md"):
        shutil.copy2(md, dst / md.name)
        count += 1
    return count


def _copy_hooks(data_dir: Path, project: Path) -> int:
    src = data_dir / "hooks"
    dst = project / ".claude" / "hooks"
    dst.mkdir(parents=True, exist_ok=True)
    count = 0
    for py in src.glob("*.py"):
        shutil.copy2(py, dst / py.name)
        count += 1
    return count


def _copy_references(data_dir: Path, project: Path) -> int:
    """Copy GDScript reference files to project."""
    src = data_dir / "references"
    if not src.exists():
        return 0
    dst = project / "references"
    dst.mkdir(parents=True, exist_ok=True)
    count = 0
    for md in src.glob("*.md"):
        shutil.copy2(md, dst / md.name)
        count += 1
    return count


def _get_python_command() -> str:
    """Get the correct Python command for the current platform."""
    if sys.platform == "win32":
        return "python"
    return "python3"


def _generate_settings_json(project: Path) -> None:
    settings_file = project / ".claude" / "settings.json"
    python_cmd = _get_python_command()

    settings = {
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "mcp__godot__.*",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/check_connection.py",
                            "timeout": 3,
                            "statusMessage": "Checking Godot connection..."
                        }
                    ]
                },
                {
                    "matcher": "Edit|Write",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/block_tscn_edit.py",
                            "timeout": 3,
                            "statusMessage": "Checking file type..."
                        }
                    ]
                }
            ],
            "SessionStart": [
                {
                    "matcher": "compact",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/compact_context.py",
                            "timeout": 5,
                            "statusMessage": "Re-injecting project context..."
                        }
                    ]
                }
            ],
            "PreCompact": [
                {
                    "matcher": "",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/pre_compact.py",
                            "timeout": 5,
                            "statusMessage": "Saving project state..."
                        }
                    ]
                }
            ],
            "PostToolUse": [
                {
                    "matcher": "mcp__godot__godot_write_script",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/gdscript_lint.py",
                            "timeout": 5,
                            "statusMessage": "Linting GDScript..."
                        }
                    ]
                },
                {
                    "matcher": "Edit|Write",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/check_project_sync.py",
                            "timeout": 3,
                            "statusMessage": "Syncing project state..."
                        }
                    ]
                }
            ]
        }
    }

    # Merge with existing settings if present
    if settings_file.exists():
        try:
            existing = json.loads(settings_file.read_text())
            existing.setdefault("hooks", {})
            existing["hooks"].update(settings["hooks"])
            settings = existing
        except (json.JSONDecodeError, OSError):
            pass

    settings_file.write_text(json.dumps(settings, indent=2) + "\n")


def _write_version(project: Path, version: str) -> None:
    vfile = project / "addons" / "golem-mcp" / "version.txt"
    vfile.write_text(version + "\n")


GOLEM_CLAUDE_MD_SECTION = """## Golem MCP

Ce projet utilise Golem MCP pour la communication Claude Code ↔ Godot.

### Routing
Pour toute tâche de construction, modification ou debug Godot, invoquer `/godot` d'abord.
Le routeur analyse l'intention, scanne le projet, et dispatche vers le bon skill.

### Session start
Au début de chaque session, lire `CHECKPOINT.md` s'il existe.
Afficher : Phase, Focus, Vertical Slice %, et suggestion d'action.
Si `GAME_SEQUENCE.yaml` existe, le mentionner et afficher le nombre de states/loops.

### Proactive behaviors
| Quand | Action |
|-------|--------|
| L'user parle de features sans plan | Proposer `/godot-plan` |
| Le plan existe mais pas de séquence | Proposer `/godot-sequence` |
| La séquence existe mais pas de simulation | Proposer `/godot-simulate` |
| La simulation est faite, pas de gate | Proposer `/godot-gate` |
| L'user part sur du code sans gate PASS | Rappeler : "La séquence n'a pas été validée" |
| L'user veut construire une UI | Proposer le pipeline : `/godot-composition` → `/godot-theme` → `/godot-behavior` |
| Gate PASS, pas d'architecture | Proposer `/godot-architecture` |
| L'user change de scope en plein milieu | Signaler le changement de scope |
| L'user refait un truc pour la 3e fois | Dire : "C'est shippable ? Alors on ship." |
| Discussion de 3+ échanges sur un sujet | Proposer de documenter dans GAME_PLAN.md ou GAME_SEQUENCE.yaml |
| Fin de session significative | Proposer `/checkpoint` |
| L'user écrit `Engine.time_scale` | Vérifier compensation player (`delta / Engine.time_scale`) |
| Une action retourne `false` sans signal | Alerter : "silent failure = bug UX, émettre un signal" |
| L'user crée un popup/overlay UI | Proposer le pattern programmatique (CanvasLayer + set_script) |

### GDScript guardrails
AVANT d'écrire ou modifier du GDScript, appliquer systématiquement ces règles :
- **`:=` interdit sur sources Variant** — dict[key], .duplicate(), ternaire `A if x else B`, comparaison `==`, load(), instantiate(), get_node_or_null(), `{}`, `[]` → toujours `: Type =`
- **`:=` safe** — littéraux (`5`, `"text"`), constructeurs (`Vector2()`, `Color()`), `.new()`, `preload()`, `randf_range()`, `clampf()`, `create_tween()`, `const`
- **`global_position` uniquement APRÈS `add_child()`** — avant, l'assignation est ignorée. `position` locale OK avant si parent à (0,0)
- **`add_child()` dans callback Area2D/Body2D** → `call_deferred("add_child", node)` (physics flush crash sinon)
- **`create_tween()` sur node qui va mourir** → `get_tree().create_tween()` (lié à l'arbre, survit au node)
- **`queue_free()` = fin de frame** — node existe encore, reçoit signaux/callbacks → `is_instance_valid()` + `is_queued_for_deletion()` avant usage
- **Signaux synchrones** — `emit()` exécute TOUS les handlers avant la ligne suivante. Ordre d'émission = comportement
- **Node typé `Node`/`Node2D` au lieu du vrai type** → warnings "member not declared". Fix : cast `as Player`, ou `has_method()`, ou pas de type annotation
- **Tween pendant pause = 2 conditions** — `PROCESS_MODE_ALWAYS` sur le node + `tw.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS)`. Un seul ne suffit pas
- **`get_theme_stylebox()` = ref partagée** — `.duplicate()` avant modification, sinon toutes les instances changent
- **Données système → UI = `.duplicate(true)`** — deep copy. L'UI est read-only, communique via signal

### Context hygiene
Avant tout `/clear`, exécuter `/checkpoint` d'abord.

### Skills disponibles
| Skill | Quand l'utiliser |
|-------|-----------------|
| `/godot` | Routeur — dispatche vers le bon skill selon l'intention |
| `/init` | Bootstrapper un nouveau projet (vision → livrables → structure) |
| `/godot-plan` | Planifier une feature — interroger, structurer, mini-PRD |
| `/godot-sequence` | Définir le game flow en YAML (loops, states, arc émotionnel) |
| `/godot-simulate` | Paper-prototyper une séquence (friction, balance, edge cases) |
| `/godot-gate` | Valider la readiness avant construction (auto-checks + questions) |
| `/godot-architecture` | Designer l'architecture modulaire (EventBus, components, scripts) |
| `/godot-scene` | Construire une scène Godot via les tools MCP |
| `/godot-composition` | Structurer une UI (tokens, containers, layout, torture tests) |
| `/godot-theme` | Créer un système de design tokens via Theme resource |
| `/godot-behavior` | Ajouter états, transitions, tweens et feedback loops à une UI |
| `/godot-juice` | Ajouter du game feel (screenshake, freeze, squash, hit effects) |
| `/godot-vfx` | Créer des effets visuels (particules, shaders, lighting) |
| `/godot-audio` | Configurer l'audio (bus, music manager, SFX pool, spatial) |
| `/godot-save` | Implémenter save/load (settings, game state, slots, migration) |
| `/godot-camera` | Configurer la caméra 2D (follow, zoom, limites, cinematic) |
| `/godot-test` | Tests automatisés (input simulation + logs + vérification) |
| `/godot-debug` | Diagnostiquer et fixer un problème étape par étape |
| `/godot-design-audit` | Auditer l'UX via screenshots et produire un rapport |
| `/godot-status` | Scanner l'état du projet, générer DEVLOG, tracker le progrès |
| `/deliverable` | Focus sur un livrable spécifique jusqu'à complétion |
| `/checkpoint` | Snapshot de l'état projet dans CHECKPOINT.md |
| `/references` | Extraire les patterns réutilisables du projet |
"""

GOLEM_CLAUDE_MD_MARKER = "## Golem MCP"


def _inject_claude_md(project: Path) -> bool:
    """Inject Golem MCP section into project's CLAUDE.md.

    Creates the file if it doesn't exist.
    Replaces the section if it already exists.
    Appends the section if the file exists but doesn't contain it.

    Returns True if the file was modified/created.
    """
    claude_md = project / "CLAUDE.md"

    if not claude_md.exists():
        claude_md.write_text(GOLEM_CLAUDE_MD_SECTION)
        return True

    content = claude_md.read_text()

    if GOLEM_CLAUDE_MD_MARKER in content:
        # Replace existing section: find start and next ## heading
        lines = content.split("\n")
        start_idx = None
        end_idx = None
        for i, line in enumerate(lines):
            if line.strip() == GOLEM_CLAUDE_MD_MARKER:
                start_idx = i
            elif start_idx is not None and i > start_idx and line.startswith("## "):
                end_idx = i
                break

        if start_idx is not None:
            if end_idx is None:
                end_idx = len(lines)
            new_lines = lines[:start_idx] + GOLEM_CLAUDE_MD_SECTION.strip().split("\n") + [""] + lines[end_idx:]
            claude_md.write_text("\n".join(new_lines))
            return True
    else:
        # Append section
        separator = "\n" if content.endswith("\n") else "\n\n"
        claude_md.write_text(content + separator + GOLEM_CLAUDE_MD_SECTION)
        return True

    return False


def _build_server_config(local_path: str | None) -> dict:
    """Build the MCP server configuration."""
    if local_path:
        return {
            "type": "stdio",
            "command": "uv",
            "args": ["run", "--directory", local_path, "golem-mcp"],
        }
    return {
        "type": "stdio",
        "command": "uvx",
        "args": ["golem-mcp"],
    }


def _generate_agent_config(project: Path, agent: str, local_path: str | None) -> str | None:
    """Generate MCP config for the specified agent.

    Returns:
        Path to the generated config file, or None if manual setup required.
    """
    config = AGENT_CONFIGS.get(agent)
    if not config:
        print(f"✗ Unknown agent: {agent}")
        print(f"  Available: {', '.join(AGENT_CONFIGS.keys())}")
        sys.exit(1)

    # Manual setup required (e.g., Cline)
    if config.get("manual"):
        return None

    path_fn: Callable[[Path], Path] = config["path"]
    config_path = path_fn(project)
    key = config["key"]

    # Ensure parent directory exists
    config_path.parent.mkdir(parents=True, exist_ok=True)

    # Load existing config or create new
    if config_path.exists():
        try:
            data = json.loads(config_path.read_text())
        except (json.JSONDecodeError, OSError):
            data = {}
    else:
        data = {}

    # Add or update the godot server config
    data.setdefault(key, {})
    data[key]["godot"] = _build_server_config(local_path)

    config_path.write_text(json.dumps(data, indent=2) + "\n")
    return str(config_path)


def _generate_mcp_json(project: Path, local_path: str | None) -> None:
    """Generate .mcp.json for Claude Code (legacy function for backwards compatibility)."""
    _generate_agent_config(project, "claude", local_path)


def _check_existing(project: Path, force: bool) -> bool:
    """Return True if we should proceed."""
    addon_dir = project / "addons" / "golem-mcp"
    if addon_dir.exists() and not force:
        answer = input("Golem MCP already installed. Overwrite? [y/N] ").strip().lower()
        return answer in ("y", "yes")
    return True


# ══════════════════════════════════════════════════════════════════════════════
# Interactive mode helpers
# ══════════════════════════════════════════════════════════════════════════════

def _detect_agents() -> list[str]:
    """Detect installed AI coding assistants on the system."""
    agents = []

    # Claude Code
    if shutil.which("claude"):
        agents.append("claude")

    # Cursor (check for app on macOS/Windows)
    if sys.platform == "darwin":
        if Path("/Applications/Cursor.app").exists():
            agents.append("cursor")
    elif sys.platform == "win32":
        cursor_paths = [
            Path(os.environ.get("LOCALAPPDATA", "")) / "Programs" / "cursor" / "Cursor.exe",
            Path(os.environ.get("PROGRAMFILES", "")) / "Cursor" / "Cursor.exe",
        ]
        if any(p.exists() for p in cursor_paths):
            agents.append("cursor")
    else:  # Linux
        # Check for cursor binary, AppImage, or Flatpak
        local_bin = Path.home() / ".local" / "bin"
        cursor_found = (
            shutil.which("cursor") or
            (local_bin.exists() and any(local_bin.glob("cursor*"))) or
            Path("/var/lib/flatpak/app/com.cursor.Cursor").exists()
        )
        if cursor_found:
            agents.append("cursor")

    # Windsurf (Codeium)
    windsurf_config = Path.home() / ".codeium" / "windsurf"
    if windsurf_config.exists():
        agents.append("windsurf")

    # Continue.dev (VS Code extension — check config dir)
    continue_config = Path.home() / ".continue"
    if continue_config.exists():
        agents.append("continue")

    # Cline (VS Code extension — harder to detect, add if others are found)
    # Just include it as an option if we detect VS Code extensions
    vscode_extensions = Path.home() / ".vscode" / "extensions"
    if vscode_extensions.exists():
        for ext in vscode_extensions.iterdir():
            if "cline" in ext.name.lower() or "claude-dev" in ext.name.lower():
                agents.append("cline")
                break

    return agents


def _detect_godot_project() -> Path | None:
    """Find a Godot project in current directory or immediate subdirectories."""
    cwd = Path.cwd()

    # Check current directory
    if (cwd / "project.godot").exists():
        return cwd

    # Check immediate subdirectories
    for child in cwd.iterdir():
        if child.is_dir() and (child / "project.godot").exists():
            return child

    return None


def _get_godot_version(project: Path) -> str | None:
    """Extract Godot version from project.godot file."""
    project_file = project / "project.godot"
    if not project_file.exists():
        return None

    try:
        content = project_file.read_text()
        for line in content.splitlines():
            # Look for config/features line which contains version
            if "config/features" in line:
                # Format: config/features=PackedStringArray("4.3", "GL Compatibility")
                if "4.4" in line:
                    return "4.4"
                elif "4.3" in line:
                    return "4.3"
                elif "4.2" in line:
                    return "4.2"
                elif "4.1" in line:
                    return "4.1"
                elif "4.0" in line:
                    return "4.0"
        return "4.x"
    except OSError:
        return None


def _prompt_choice(question: str, options: list[tuple[str, str]], default: int = 0) -> int:
    """Display a menu and return the selected index.

    Args:
        question: Question to display
        options: List of (label, description) tuples
        default: Default selection index

    Returns:
        Selected index, or -1 if cancelled
    """
    print(f"\n? {question}")
    for i, (label, desc) in enumerate(options):
        marker = "●" if i == default else "○"
        if desc:
            print(f"  {marker} {label} — {desc}")
        else:
            print(f"  {marker} {label}")

    print()
    try:
        answer = input(f"  Enter choice [1-{len(options)}] (default: {default + 1}): ").strip()
        if not answer:
            return default
        idx = int(answer) - 1
        if 0 <= idx < len(options):
            return idx
        return default
    except (ValueError, EOFError, KeyboardInterrupt):
        return -1


def _prompt_confirm(question: str, default: bool = True) -> bool:
    """Prompt for yes/no confirmation."""
    suffix = "[Y/n]" if default else "[y/N]"
    try:
        answer = input(f"\n? {question} {suffix} ").strip().lower()
        if not answer:
            return default
        return answer in ("y", "yes")
    except (EOFError, KeyboardInterrupt):
        return False


def _interactive_install() -> tuple[Path | None, str]:
    """Run interactive installation wizard.

    Returns:
        Tuple of (project_path, agent_name) or (None, "") if cancelled
    """
    from golem_mcp import _print_banner

    _print_banner()
    print("  Interactive Setup")
    print("  " + "─" * 50)
    print()
    print("  Detecting environment...")

    # Detect agents
    detected_agents = _detect_agents()
    all_agents = list(AGENT_CONFIGS.keys())

    # Build agent options
    agent_options = []
    for agent_id in all_agents:
        config = AGENT_CONFIGS[agent_id]
        name = config.get("name", agent_id)
        detected = "(detected)" if agent_id in detected_agents else ""
        agent_options.append((name, detected))

    # Select agent
    default_agent_idx = 0
    if detected_agents:
        # Prefer first detected agent
        for i, agent_id in enumerate(all_agents):
            if agent_id in detected_agents:
                default_agent_idx = i
                break

    agent_idx = _prompt_choice("Which AI assistant are you using?", agent_options, default_agent_idx)
    if agent_idx < 0:
        print("\nAborted.")
        return None, ""

    selected_agent = all_agents[agent_idx]

    # Detect Godot project
    detected_project = _detect_godot_project()

    if detected_project:
        version = _get_godot_version(detected_project) or "4.x"
        project_options = [
            (f"./{detected_project.name}", f"Godot {version} — detected"),
            ("Enter path manually", ""),
        ]
        project_idx = _prompt_choice("Which Godot project?", project_options, 0)

        if project_idx < 0:
            print("\nAborted.")
            return None, ""
        elif project_idx == 0:
            project = detected_project
        else:
            try:
                path_str = input("\n  Enter project path: ").strip()
                project = Path(path_str).resolve()
            except (EOFError, KeyboardInterrupt):
                print("\nAborted.")
                return None, ""
    else:
        print("\n  No Godot project detected in current directory.")
        try:
            path_str = input("  Enter project path: ").strip()
            if not path_str:
                print("\nAborted.")
                return None, ""
            project = Path(path_str).resolve()
        except (EOFError, KeyboardInterrupt):
            print("\nAborted.")
            return None, ""

    # Confirm
    agent_name = AGENT_CONFIGS[selected_agent].get("name", selected_agent)
    if not _prompt_confirm(f"Install Golem MCP in {project} for {agent_name}?"):
        print("\nAborted.")
        return None, ""

    return project, selected_agent


def _parse_flag_value(argv: list[str], flag: str) -> str | None:
    """Extract a flag value from argv (e.g., --local /path)."""
    for i, a in enumerate(argv):
        if a == flag and i + 1 < len(argv):
            return argv[i + 1]
    return None


def run_install(argv: list[str]) -> None:
    # Parse flags
    force = "--force" in argv
    skip_skills = "--skip-skills" in argv
    local_path = _parse_flag_value(argv, "--local")
    agent = _parse_flag_value(argv, "--agent") or "claude"

    # Get positional args (exclude flags and their values)
    skip_next = False
    args = []
    for i, a in enumerate(argv):
        if skip_next:
            skip_next = False
            continue
        if a.startswith("--"):
            if a in ("--local", "--agent"):
                skip_next = True
            continue
        args.append(a)

    # Interactive mode if no project specified
    if not args:
        project, agent = _interactive_install()
        if project is None:
            return
    else:
        project = Path(args[0]).resolve()

    version = _get_version()

    _validate_project(project)

    if not _check_existing(project, force):
        print("Aborted.")
        return

    data_dir = _get_data_dir()

    # Copy plugin
    plugin_count = _copy_plugin(data_dir, project)
    _write_version(project, version)

    # Copy skills (only for Claude Code, which uses .claude/commands/)
    skills_count = 0
    if not skip_skills and agent == "claude":
        skills_count = _copy_skills(data_dir, project)

    # Copy hooks (only for Claude Code)
    hooks_count = 0
    if agent == "claude":
        hooks_count = _copy_hooks(data_dir, project)
        _generate_settings_json(project)

    # Copy GDScript references (only for Claude Code)
    refs_count = 0
    if agent == "claude":
        refs_count = _copy_references(data_dir, project)

    # Inject CLAUDE.md routing instruction (Claude Code only)
    claude_md_updated = False
    if agent == "claude":
        claude_md_updated = _inject_claude_md(project)

    # Generate agent-specific config
    config_path = _generate_agent_config(project, agent, local_path)

    agent_config = AGENT_CONFIGS.get(agent, {})
    agent_name = agent_config.get("name", agent)

    # Summary
    print()
    print(f"✓ Golem MCP v{version} installed in {project}")
    print(f"  Agent:   {agent_name}")
    print(f"  Plugin:  addons/golem-mcp/ ({plugin_count} files)")
    if skills_count:
        print(f"  Skills:  .claude/commands/ ({skills_count} files)")
    if hooks_count:
        print(f"  Hooks:   .claude/hooks/ ({hooks_count} files)")
    if refs_count:
        print(f"  Refs:    references/ ({refs_count} files)")
    if claude_md_updated:
        print(f"  Routing: CLAUDE.md (skill routing instruction)")
    if config_path:
        print(f"  Config:  {config_path}")

    # Agent-specific instructions
    instructions = AGENT_INSTRUCTIONS.get(agent, "")
    if instructions:
        print(instructions)


# ══════════════════════════════════════════════════════════════════════════════
# Uninstall
# ══════════════════════════════════════════════════════════════════════════════

# Skills that are part of Golem MCP (to avoid removing user's own skills)
GOLEM_SKILLS = [
    "godot.md",
    "godot-plan.md",
    "godot-sequence.md",
    "godot-simulate.md",
    "godot-gate.md",
    "godot-architecture.md",
    "godot-scene.md",
    "godot-composition.md",
    "godot-behavior.md",
    "godot-juice.md",
    "godot-vfx.md",
    "godot-audio.md",
    "godot-save.md",
    "godot-camera.md",
    "godot-theme.md",
    "godot-test.md",
    "godot-debug.md",
    "godot-design-audit.md",
    "godot-status.md",
    "init.md",
    "checkpoint.md",
    "references.md",
    "deliverable.md",
    "golem-dev.md",
    "INDEX.md",
]

# Hooks that are part of Golem MCP
GOLEM_HOOKS = [
    "check_connection.py",
    "block_tscn_edit.py",
    "gdscript_lint.py",
    "copy_skills.py",
    "check_project_sync.py",
    "compact_context.py",
    "pre_compact.py",
]


def _remove_plugin(project: Path) -> bool:
    """Remove the Golem MCP plugin directory."""
    plugin_dir = project / "addons" / "golem-mcp"
    if plugin_dir.exists():
        shutil.rmtree(plugin_dir)
        return True
    return False


def _remove_skills(project: Path) -> int:
    """Remove Golem MCP skills from .claude/commands/."""
    commands_dir = project / ".claude" / "commands"
    if not commands_dir.exists():
        return 0

    count = 0
    for skill in GOLEM_SKILLS:
        skill_file = commands_dir / skill
        if skill_file.exists():
            skill_file.unlink()
            count += 1

    # Remove directory if empty
    if commands_dir.exists() and not any(commands_dir.iterdir()):
        commands_dir.rmdir()

    return count


def _remove_hooks(project: Path) -> int:
    """Remove Golem MCP hooks from .claude/hooks/."""
    hooks_dir = project / ".claude" / "hooks"
    if not hooks_dir.exists():
        return 0

    count = 0
    for hook in GOLEM_HOOKS:
        hook_file = hooks_dir / hook
        if hook_file.exists():
            hook_file.unlink()
            count += 1

    # Remove directory if empty
    if hooks_dir.exists() and not any(hooks_dir.iterdir()):
        hooks_dir.rmdir()

    return count


def _clean_mcp_configs(project: Path) -> list[str]:
    """Remove 'godot' entry from all known MCP config files."""
    cleaned = []

    for agent_id, config in AGENT_CONFIGS.items():
        if config.get("manual"):
            continue

        path_fn = config["path"]
        config_path = path_fn(project)

        if not config_path.exists():
            continue

        try:
            data = json.loads(config_path.read_text())
            key = config["key"]

            if key in data and "godot" in data[key]:
                del data[key]["godot"]

                # Remove key if empty
                if not data[key]:
                    del data[key]

                # Remove file if empty, else rewrite
                if not data or data == {}:
                    config_path.unlink()
                else:
                    config_path.write_text(json.dumps(data, indent=2) + "\n")

                cleaned.append(str(config_path))
        except (json.JSONDecodeError, OSError):
            pass

    return cleaned


def _clean_settings_json(project: Path) -> bool:
    """Remove Golem MCP hooks from .claude/settings.json."""
    settings_file = project / ".claude" / "settings.json"
    if not settings_file.exists():
        return False

    try:
        data = json.loads(settings_file.read_text())
        modified = False

        if "hooks" in data:
            def _is_golem_hook_entry(entry: dict) -> bool:
                """Check if a hook entry belongs to Golem MCP."""
                matcher = entry.get("matcher", "")
                if matcher.startswith("mcp__godot__"):
                    return True
                for hook in entry.get("hooks", []):
                    cmd = hook.get("command", "")
                    if "golem" in cmd.lower() or ".claude/hooks/" in cmd:
                        return True
                return False

            # Remove Golem hooks from all event types
            for event_type in ["PreToolUse", "PostToolUse", "SessionStart", "PreCompact"]:
                if event_type in data["hooks"]:
                    original_len = len(data["hooks"][event_type])
                    data["hooks"][event_type] = [
                        h for h in data["hooks"][event_type]
                        if not _is_golem_hook_entry(h)
                    ]
                    if len(data["hooks"][event_type]) != original_len:
                        modified = True
                    if not data["hooks"][event_type]:
                        del data["hooks"][event_type]

            # Remove hooks key if empty
            if not data["hooks"]:
                del data["hooks"]

        if modified:
            if data:
                settings_file.write_text(json.dumps(data, indent=2) + "\n")
            else:
                settings_file.unlink()
            return True

    except (json.JSONDecodeError, OSError):
        pass

    return False


def _clean_claude_md(project: Path) -> bool:
    """Remove Golem MCP section from CLAUDE.md."""
    claude_md = project / "CLAUDE.md"
    if not claude_md.exists():
        return False

    content = claude_md.read_text()
    if GOLEM_CLAUDE_MD_MARKER not in content:
        return False

    lines = content.split("\n")
    start_idx = None
    end_idx = None
    for i, line in enumerate(lines):
        if line.strip() == GOLEM_CLAUDE_MD_MARKER:
            start_idx = i
        elif start_idx is not None and i > start_idx and line.startswith("## "):
            end_idx = i
            break

    if start_idx is None:
        return False

    if end_idx is None:
        end_idx = len(lines)

    # Remove section and any trailing blank lines
    new_lines = lines[:start_idx] + lines[end_idx:]
    while new_lines and not new_lines[-1].strip():
        new_lines.pop()

    new_content = "\n".join(new_lines) + "\n" if new_lines else ""

    if new_content.strip():
        claude_md.write_text(new_content)
    else:
        claude_md.unlink()

    return True


def run_uninstall(argv: list[str]) -> None:
    """Uninstall Golem MCP from a Godot project."""
    force = "--force" in argv
    args = [a for a in argv if not a.startswith("--")]

    if not args:
        # Try to detect project in current directory
        project = _detect_godot_project()
        if not project:
            print("Usage: golem-mcp uninstall <project_path>")
            print()
            print("No Godot project found in current directory.")
            sys.exit(1)
    else:
        project = Path(args[0]).resolve()

    _validate_project(project)

    # Check if Golem MCP is installed
    plugin_dir = project / "addons" / "golem-mcp"
    if not plugin_dir.exists():
        print(f"✗ Golem MCP is not installed in {project}")
        return

    # Confirm
    if not force:
        answer = input(f"Uninstall Golem MCP from {project}? [y/N] ").strip().lower()
        if answer not in ("y", "yes"):
            print("Aborted.")
            return

    # Remove components
    removed_plugin = _remove_plugin(project)
    removed_skills = _remove_skills(project)
    removed_hooks = _remove_hooks(project)
    cleaned_configs = _clean_mcp_configs(project)
    cleaned_settings = _clean_settings_json(project)
    cleaned_claude_md = _clean_claude_md(project)

    # Clean up empty .claude directory
    claude_dir = project / ".claude"
    if claude_dir.exists():
        # Check if only empty subdirs remain
        remaining = list(claude_dir.rglob("*"))
        remaining_files = [f for f in remaining if f.is_file()]
        if not remaining_files:
            shutil.rmtree(claude_dir)

    # Summary
    print()
    print(f"✓ Golem MCP uninstalled from {project}")
    if removed_plugin:
        print("  Removed: addons/golem-mcp/")
    if removed_skills:
        print(f"  Removed: {removed_skills} skill files")
    if removed_hooks:
        print(f"  Removed: {removed_hooks} hook files")
    if cleaned_configs:
        for cfg in cleaned_configs:
            print(f"  Cleaned: {cfg}")
    if cleaned_settings:
        print("  Cleaned: .claude/settings.json")
    if cleaned_claude_md:
        print("  Cleaned: CLAUDE.md (Golem MCP section removed)")
