"""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: /think, /build, /fix, or /learn
""",
    "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: dev checkout (package/) or installed (golem_mcp_data/)."""
    # Dev checkout — assets in package/
    repo = Path(__file__).parent
    pkg = repo / "package"
    if (pkg / "godot-plugin").is_dir():
        return pkg

    # Installed via pip/uvx — assets in golem_mcp_data/
    pkg_data = repo / "golem_mcp_data"
    if (pkg_data / "godot-plugin").is_dir():
        return pkg_data

    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_commands(data_dir: Path, project: Path) -> int:
    """Copy user-facing slash commands to .claude/commands/."""
    src = data_dir / "commands"
    if not src.exists():
        print(f"  ⚠ Commands 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_skills(data_dir: Path, project: Path) -> int:
    """Copy internal skills to .claude/skills/."""
    src = data_dir / "skills"
    if not src.exists():
        return 0

    dst = project / ".claude" / "skills"
    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_tools(data_dir: Path, project: Path) -> int:
    """Copy tooling scripts to .claude/tools/."""
    src = data_dir / "tools"
    if not src.exists():
        return 0

    dst = project / ".claude" / "tools"
    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 _create_working_dirs(project: Path) -> list[str]:
    """Create deliverables/ and learnings/ with .gitkeep."""
    created = []
    for dirname in ("deliverables", "learnings"):
        dirpath = project / dirname
        if not dirpath.exists():
            dirpath.mkdir(parents=True, exist_ok=True)
            (dirpath / ".gitkeep").touch()
            created.append(dirname)
    return created


def _copy_rules(data_dir: Path, project: Path) -> int:
    """Copy auto-loaded rules to .claude/rules/."""
    src = data_dir / "rules"
    if not src.exists():
        return 0

    dst = project / ".claude" / "rules"
    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 _copy_recipes(data_dir: Path, project: Path) -> int:
    """Copy implementation recipes to project."""
    src = data_dir / "recipes"
    if not src.exists():
        return 0
    dst = project / "recipes"
    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..."
                        }
                    ]
                }
            ]
        }
    }

    # Enable Agent Teams experimental feature
    settings["env"] = {
        "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
    }

    # 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"])
            existing["env"] = settings.get("env", {})
            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")


def _build_claude_md_section() -> str:
    """Build CLAUDE.md section with dynamic counts from package/."""
    data_dir = _get_data_dir()
    n_commands = len(list((data_dir / "commands").glob("*.md")))
    n_skills = len([f for f in (data_dir / "skills").glob("*.md") if f.name not in ("godot.md", "INDEX.md")])
    return f"""## Golem MCP

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

### Commands ({n_commands})
Points d'entrée du workflow :

| Commande | Rôle |
|----------|------|
| `/think` | Brainstorm, design, planification — read-only, pas de code |
| `/fix` | Debug, diagnostic, réparation |
| `/build` | Implémentation de livrables — pre-flight, manifest, code, validation |
| `/learn` | Extraction de feedback session dans `learnings/` |
| `/ship` | Version sync + clean dist + build PyPI |
| `/init` | Bootstrap un nouveau projet Godot |
| `/checkpoint` | Snapshot de l'état projet |
| `/deliverable` | Focus sur un livrable spécifique |
| `/references` | Extraire des patterns réutilisables |

### Skills ({n_skills} — internes)
Les commands routent vers les skills dans `.claude/skills/`. Le routeur principal est `/godot` qui dispatch vers le bon skill.

**Annonce** : quand un skill est consulté, annoncer sur une ligne. Format : `[SKILL] Je consulte .claude/skills/godot-xxx.md.`

| Quand | Obligation |
|-------|------------|
| Game feel (screenshake, freeze, squash, trail) | Consulter `.claude/skills/godot-juice.md` |
| VFX (particules, shaders, lighting, glow) | Consulter `.claude/skills/godot-vfx.md` |
| Audio (bus, musique, SFX, spatial) | Consulter `.claude/skills/godot-audio.md` |
| UI composition (tokens, containers, layout) | Consulter `.claude/skills/godot-composition.md` |
| UI behavior (états, transitions, tweens) | Consulter `.claude/skills/godot-behavior.md` |
| Caméra (follow, zoom, limites) | Consulter `.claude/skills/godot-camera.md` |
| Save/load (settings, persistence) | Consulter `.claude/skills/godot-save.md` |
| Architecture (EventBus, components) | Consulter `.claude/skills/godot-architecture.md` |
| Construction de scène via MCP | Consulter `.claude/skills/godot-scene.md` |
| Features sans plan | Consulter `.claude/skills/godot-plan.md` — pas de code sans mini-PRD |
| Game flow, boucles | Consulter `.claude/skills/godot-sequence.md` |
| Tester des idées | Consulter `.claude/skills/godot-simulate.md` |
| Validation pré-construction | Consulter `.claude/skills/godot-gate.md` |
| Debug/fix | Consulter `.claude/skills/godot-debug.md` |
| Test automatisé | Consulter `.claude/skills/godot-test.md` |
| Design tokens / Theme | Consulter `.claude/skills/godot-theme.md` |
| Audit UX | Consulter `.claude/skills/godot-design-audit.md` |
| État du projet | Consulter `.claude/skills/godot-status.md` |

### Proactive behaviors
| Quand | Action |
|-------|--------|
| 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." |
| Deliverables découpés par composant technique | Restructurer par verbe joueur |
| `@export var X` dans extends NativeClass | Vérifier que le nom ne masque pas un membre natif |

### GDScript guardrails
AVANT d'écrire ou modifier du GDScript, appliquer systématiquement ces règles :
- **`:=` interdit sur sources Variant** — dict[key], .duplicate(), ternaire, `==`, load(), instantiate(), get_node_or_null(), `{{}}`, `[]` → `: Type =`
- **`:=` safe** — littéraux, constructeurs typés, `.new()`, `preload()`, `randf_range()`, `clampf()`, `create_tween()`, `const`
- **`global_position` uniquement APRÈS `add_child()`**
- **`add_child()` dans callback Area2D/Body2D** → `call_deferred("add_child", node)`
- **`create_tween()` sur node qui va mourir** → `get_tree().create_tween()`
- **`queue_free()` = fin de frame** → `is_instance_valid()` + `is_queued_for_deletion()` avant usage
- **Signaux synchrones** — `emit()` exécute tous les handlers avant la ligne suivante
- **Tween pendant pause** = `PROCESS_MODE_ALWAYS` + `TWEEN_PAUSE_PROCESS`
- **`get_theme_stylebox()` = ref partagée** → `.duplicate()` avant modification
- **Données système → UI** = `.duplicate(true)` (deep copy)

### Règle fondamentale : fichiers vs API Godot
**Ne JAMAIS éditer `.tscn` ou `project.godot` sur disque.** Godot écrase les changements disque.
- Script `.gd` → Edit/Write (auto-reload)
- Node de scène → `godot_add_node` / `godot_delete_node` / `godot_update_property` → `godot_save_scene`
- Project setting → `godot_project_settings action=set`
- Séquence : `stop_scene` → API calls → `save_scene` → `get_scene_tree` → `play_scene`

### Recipes — OBLIGATOIRE
Consulter `recipes/INDEX.md` AVANT d'implémenter toute mécanique, effet visuel, shader ou système gameplay.
Annonce : `[RECIPE] Je consulte recipes/xxx.md avant d'implémenter.`

### References
- `references/gdscript_pitfalls.md` — Pièges GDScript 4.x
- `references/workflow_patterns.md` — Multi-agent, patterns archi, signal-driven UI
"""

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"

    section = _build_claude_md_section()

    if not claude_md.exists():
        claude_md.write_text(section, encoding="utf-8")
        return True

    content = claude_md.read_text(encoding="utf-8")

    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] + section.strip().split("\n") + [""] + lines[end_idx:]
            claude_md.write_text("\n".join(new_lines), encoding="utf-8")
            return True
    else:
        # Append section
        separator = "\n" if content.endswith("\n") else "\n\n"
        claude_md.write_text(content + separator + section, encoding="utf-8")
        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 commands (only for Claude Code, which uses .claude/commands/)
    commands_count = 0
    if not skip_skills and agent == "claude":
        commands_count = _copy_commands(data_dir, project)

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

    # Copy tools (only for Claude Code)
    tools_count = 0
    if agent == "claude":
        tools_count = _copy_tools(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 rules (only for Claude Code, which uses .claude/rules/)
    rules_count = 0
    if agent == "claude":
        rules_count = _copy_rules(data_dir, project)

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

    # Copy implementation recipes (only for Claude Code)
    recipes_count = 0
    if agent == "claude":
        recipes_count = _copy_recipes(data_dir, project)

    # Create working directories (only for Claude Code)
    working_dirs = []
    if agent == "claude":
        working_dirs = _create_working_dirs(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 commands_count:
        print(f"  Commands: .claude/commands/ ({commands_count} files)")
    if skills_count:
        print(f"  Skills:   .claude/skills/ ({skills_count} files)")
    if tools_count:
        print(f"  Tools:    .claude/tools/ ({tools_count} files)")
    if rules_count:
        print(f"  Rules:    .claude/rules/ ({rules_count} files)")
    if hooks_count:
        print(f"  Hooks:    .claude/hooks/ ({hooks_count} files)")
    if refs_count:
        print(f"  Refs:     references/ ({refs_count} files)")
    if recipes_count:
        print(f"  Recipes:  recipes/ ({recipes_count} files)")
    if working_dirs:
        print(f"  Dirs:     {', '.join(working_dirs + ['/'])}")
    if claude_md_updated:
        print("  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
# ══════════════════════════════════════════════════════════════════════════════

# Legacy files for cleanup — no longer in package/ but may exist in user projects
_LEGACY_COMMANDS = [
    "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", "golem-dev.md", "INDEX.md",
]
_LEGACY_SKILLS = [
    "plan.md", "sequence.md", "simulate.md", "gate.md", "architecture.md",
    "scene.md", "ui.md", "juice.md", "vfx.md", "audio.md", "camera.md",
    "save.md", "debug.md", "test.md",
]
_LEGACY_RULES = [
    "gdscript-pitfalls.md", "mcp-tools-pitfalls.md",
    "multi-agent-gamedev.md", "vfx-lighting.md", "audio-rules.md",
    "recipe-gate.md",
]
_LEGACY_HOOKS = ["copy_skills.py", "check_project_sync.py"]


def _get_cleanup_files(subdir: str, pattern: str, legacy: list[str] | None = None) -> set[str]:
    """Current files from package/ + legacy files for backward-compatible cleanup."""
    data_dir = _get_data_dir()
    src = data_dir / subdir
    current = {f.name for f in src.glob(pattern)} if src.exists() else set()
    return current | set(legacy or [])


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_commands(project: Path) -> int:
    """Remove Golem MCP commands from .claude/commands/."""
    commands_dir = project / ".claude" / "commands"
    if not commands_dir.exists():
        return 0

    count = 0
    for cmd in _get_cleanup_files("commands", "*.md", _LEGACY_COMMANDS):
        cmd_file = commands_dir / cmd
        if cmd_file.exists():
            cmd_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_internal_skills(project: Path) -> int:
    """Remove Golem MCP internal skills from .claude/skills/."""
    skills_dir = project / ".claude" / "skills"
    if not skills_dir.exists():
        return 0

    count = 0
    for skill in _get_cleanup_files("skills", "*.md", _LEGACY_SKILLS):
        skill_file = skills_dir / skill
        if skill_file.exists():
            skill_file.unlink()
            count += 1

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

    return count


def _remove_tools(project: Path) -> int:
    """Remove Golem MCP tools from .claude/tools/."""
    tools_dir = project / ".claude" / "tools"
    if not tools_dir.exists():
        return 0

    count = 0
    for tool in _get_cleanup_files("tools", "*.py"):
        tool_file = tools_dir / tool
        if tool_file.exists():
            tool_file.unlink()
            count += 1

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

    return count


def _remove_rules(project: Path) -> int:
    """Remove Golem MCP rules from .claude/rules/."""
    rules_dir = project / ".claude" / "rules"
    if not rules_dir.exists():
        return 0

    count = 0
    for rule in _get_cleanup_files("rules", "*.md", _LEGACY_RULES):
        rule_file = rules_dir / rule
        if rule_file.exists():
            rule_file.unlink()
            count += 1

    # Remove directory if empty
    if rules_dir.exists() and not any(rules_dir.iterdir()):
        rules_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 _get_cleanup_files("hooks", "*.py", _LEGACY_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 _remove_recipes(project: Path) -> bool:
    """Remove Golem MCP recipes directory."""
    recipes_dir = project / "recipes"
    if recipes_dir.exists():
        shutil.rmtree(recipes_dir)
        return True
    return False


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(encoding="utf-8")
    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, encoding="utf-8")
    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_commands = _remove_commands(project)
    removed_skills = _remove_internal_skills(project)
    removed_tools = _remove_tools(project)
    removed_rules = _remove_rules(project)
    removed_hooks = _remove_hooks(project)
    removed_recipes = _remove_recipes(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_commands:
        print(f"  Removed: {removed_commands} command files")
    if removed_skills:
        print(f"  Removed: {removed_skills} skill files")
    if removed_tools:
        print(f"  Removed: {removed_tools} tool files")
    if removed_rules:
        print(f"  Removed: {removed_rules} rule files")
    if removed_hooks:
        print(f"  Removed: {removed_hooks} hook files")
    if removed_recipes:
        print("  Removed: recipes/")
    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)")
