#!/usr/bin/env python3
"""
Unified Claude Code Memory Awareness Hooks Installer
====================================================

Cross-platform installer for Claude Code memory awareness hooks with support for:
- Basic memory awareness hooks (session-start, session-end)
- Natural Memory Triggers v7.1.3 (intelligent automatic memory awareness)
- Mid-conversation hooks for real-time memory injection
- Performance optimization and CLI management tools
- Smart MCP detection and DRY configuration

Replaces multiple platform-specific installers with a single Python solution.
Implements DRY principle by detecting and reusing existing Claude Code MCP configurations.

Version: Dynamically synced with main project version
"""

import os
import sys
import json
import shutil
import platform
import argparse
import subprocess
from pathlib import Path
from typing import Dict, List, Optional, Tuple

# Fix Windows console encoding for Unicode output (emojis, checkmarks)
if sys.platform == 'win32':
    try:
        # Set console to UTF-8 mode
        import ctypes
        kernel32 = ctypes.windll.kernel32
        kernel32.SetConsoleOutputCP(65001)
        kernel32.SetConsoleCP(65001)
        # Reconfigure stdout/stderr to use UTF-8
        sys.stdout.reconfigure(encoding='utf-8', errors='replace')
        sys.stderr.reconfigure(encoding='utf-8', errors='replace')
    except Exception:
        pass  # Fallback: some terminals may not support this

# Dynamic version detection from main project
def get_project_version() -> str:
    """Get version dynamically from main project (reads pyproject.toml to avoid import warnings)."""
    try:
        # Read version from pyproject.toml to avoid importing storage modules
        pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
        if pyproject_path.exists():
            with open(pyproject_path, 'r') as f:
                for line in f:
                    if line.startswith('version = '):
                        # Extract version from: version = "X.Y.Z"
                        return line.split('"')[1]

        # Fallback: try importing (may show warnings)
        src_path = Path(__file__).parent.parent / "src"
        if str(src_path) not in sys.path:
            sys.path.insert(0, str(src_path))
        from mcp_memory_service._version import __version__
        return __version__
    except Exception:
        # Fallback for standalone installations
        return "7.2.0"


class Colors:
    """ANSI color codes for terminal output."""
    GREEN = '\033[0;32m'
    YELLOW = '\033[1;33m'
    RED = '\033[0;31m'
    BLUE = '\033[0;34m'
    CYAN = '\033[0;36m'
    NC = '\033[0m'  # No Color


class HookInstaller:
    """Unified hook installer for all platforms and feature levels."""

    # Environment type constants
    CLAUDE_CODE_ENV = "claude-code"
    STANDALONE_ENV = "standalone"

    def __init__(self):
        self.script_dir = Path(__file__).parent.absolute()
        self.platform_name = platform.system().lower()
        self.claude_hooks_dir = self._detect_claude_hooks_directory()
        self.backup_dir = None

    def _detect_claude_hooks_directory(self) -> Path:
        """Detect the Claude Code hooks directory across platforms."""
        home = Path.home()

        # Primary paths by platform
        primary_paths = {
            'windows': [
                home / 'AppData' / 'Roaming' / 'Claude' / 'hooks',
                home / '.claude' / 'hooks'
            ],
            'darwin': [  # macOS
                home / '.claude' / 'hooks',
                home / 'Library' / 'Application Support' / 'Claude' / 'hooks'
            ],
            'linux': [
                home / '.claude' / 'hooks',
                home / '.config' / 'claude' / 'hooks'
            ]
        }

        # Check platform-specific paths first
        platform_paths = primary_paths.get(self.platform_name, primary_paths['linux'])

        for path in platform_paths:
            if path.exists():
                return path

        # Check if Claude Code CLI can tell us the location
        try:
            result = subprocess.run(['claude', '--help'],
                                  capture_output=True, text=True, timeout=5)
            # Look for hooks directory info in help output
            # This is a placeholder - actual Claude CLI might not provide this
        except (subprocess.SubprocessError, FileNotFoundError, subprocess.TimeoutExpired):
            pass

        # Default to standard location
        return home / '.claude' / 'hooks'

    def info(self, message: str) -> None:
        """Print info message."""
        print(f"{Colors.GREEN}[INFO]{Colors.NC} {message}")

    def warn(self, message: str) -> None:
        """Print warning message."""
        print(f"{Colors.YELLOW}[WARN]{Colors.NC} {message}")

    def error(self, message: str) -> None:
        """Print error message."""
        print(f"{Colors.RED}[ERROR]{Colors.NC} {message}")

    def success(self, message: str) -> None:
        """Print success message."""
        print(f"{Colors.BLUE}[SUCCESS]{Colors.NC} {message}")

    def header(self, message: str) -> None:
        """Print header message."""
        print(f"\n{Colors.CYAN}{'=' * 60}{Colors.NC}")
        print(f"{Colors.CYAN} {message}{Colors.NC}")
        print(f"{Colors.CYAN}{'=' * 60}{Colors.NC}\n")

    def check_prerequisites(self) -> bool:
        """Check system prerequisites for hook installation."""
        self.info("Checking prerequisites...")

        all_good = True

        # Check Claude Code CLI
        try:
            result = subprocess.run(['claude', '--version'],
                                  capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                self.success(f"Claude Code CLI found: {result.stdout.strip()}")
            else:
                self.warn("Claude Code CLI found but version check failed")
        except (subprocess.SubprocessError, FileNotFoundError, subprocess.TimeoutExpired):
            self.warn("Claude Code CLI not found in PATH")
            self.info("You can still install hooks, but some features may not work")

        # Check Node.js
        try:
            result = subprocess.run(['node', '--version'],
                                  capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                version = result.stdout.strip()
                major_version = int(version.replace('v', '').split('.')[0])
                if major_version >= 14:
                    self.success(f"Node.js found: {version} (compatible)")
                else:
                    self.error(f"Node.js {version} found, but version 14+ required")
                    all_good = False
            else:
                self.error("Node.js found but version check failed")
                all_good = False
        except (subprocess.SubprocessError, FileNotFoundError, subprocess.TimeoutExpired):
            self.error("Node.js not found - required for hook execution")
            self.info("Please install Node.js 14+ from https://nodejs.org/")
            all_good = False

        # Check Python version
        if sys.version_info < (3, 7):
            self.error(f"Python {sys.version} found, but Python 3.7+ required")
            all_good = False
        else:
            self.success(f"Python {sys.version_info.major}.{sys.version_info.minor} found (compatible)")

        return all_good

    def detect_claude_mcp_configuration(self) -> Optional[Dict]:
        """Detect existing Claude Code MCP memory server configuration."""
        self.info("Detecting existing Claude Code MCP configuration...")

        try:
            # Check if memory server is configured in Claude Code
            result = subprocess.run(['claude', 'mcp', 'get', 'memory'],
                                  capture_output=True, text=True, timeout=10)

            if result.returncode == 0:
                # Parse the output to extract configuration details
                config_info = self._parse_mcp_get_output(result.stdout)
                if config_info:
                    self.success(f"Found existing memory server: {config_info.get('command', 'Unknown')}")
                    self.success(f"Status: {config_info.get('status', 'Unknown')}")
                    self.success(f"Type: {config_info.get('type', 'Unknown')}")
                    return config_info
                else:
                    self.warn("Memory server found but configuration could not be parsed")
            else:
                self.info("No existing memory server found in Claude Code MCP configuration")

        except subprocess.TimeoutExpired:
            self.warn("Claude MCP command timed out")
        except FileNotFoundError:
            self.warn("Claude Code CLI not found - cannot detect existing MCP configuration")
        except Exception as e:
            self.warn(f"Failed to detect MCP configuration: {e}")

        return None

    def _parse_mcp_get_output(self, output: str) -> Optional[Dict]:
        """Parse the output of 'claude mcp get memory' command."""
        config = {}

        try:
            lines = output.strip().split('\n')
            for line in lines:
                line = line.strip()
                if line.startswith('Status:'):
                    config['status'] = line.replace('Status:', '').strip()
                elif line.startswith('Type:'):
                    config['type'] = line.replace('Type:', '').strip()
                elif line.startswith('Command:'):
                    config['command'] = line.replace('Command:', '').strip()
                elif line.startswith('Scope:'):
                    config['scope'] = line.replace('Scope:', '').strip()
                elif line.startswith('Environment:'):
                    config['environment'] = line.replace('Environment:', '').strip()

            # Only return config if we found essential information
            if 'command' in config and 'status' in config:
                return config

        except Exception as e:
            self.warn(f"Failed to parse MCP output: {e}")

        return None

    def detect_environment_type(self) -> str:
        """Detect if running in Claude Code vs standalone environment."""
        self.info("Detecting environment type...")

        # Check for Claude Code MCP server (indicates Claude Code is active)
        mcp_config = self.detect_claude_mcp_configuration()

        if mcp_config and 'Connected' in mcp_config.get('status', ''):
            self.success("Claude Code environment detected (MCP server active)")
            return self.CLAUDE_CODE_ENV
        else:
            self.success("Standalone environment detected (no active MCP server)")
            return self.STANDALONE_ENV

    def _detect_python_path(self) -> str:
        """Detect the appropriate Python executable path for the current platform.

        Returns:
            str: Python executable path (sys.executable for venv support, fallback to 'python3'/'python')
        """
        import sys
        import platform
        import os

        # Check Python version (must be 3.10+)
        if sys.version_info < (3, 10):
            self.warn(f"Python {sys.version_info.major}.{sys.version_info.minor} detected - code execution requires 3.10+")

        # Use sys.executable to get the current Python interpreter
        # This ensures venv Python is used if installer runs from venv
        current_python = sys.executable

        # Verify the executable exists and is accessible
        if current_python and os.path.isfile(current_python) and os.access(current_python, os.X_OK):
            self.info(f"Using Python interpreter: {current_python}")
            return current_python

        # Fallback to platform-specific defaults if sys.executable is unavailable
        self.warn("Could not detect Python executable, using platform default")
        if platform.system() == 'Windows':
            return 'python'
        else:
            return 'python3'

    def configure_protocol_for_environment(self, env_type: str) -> Dict:
        """Configure optimal protocol based on detected environment."""
        # Data-driven configuration map
        config_map = {
            self.CLAUDE_CODE_ENV: {
                "protocol": "http",
                "preferredProtocol": "http",
                "fallbackEnabled": True,
                "reason": "Claude Code environment - using HTTP to avoid MCP conflicts",
                "log_title": "📋 Protocol Configuration: HTTP (recommended for Claude Code)",
                "log_reason": "Avoids MCP server conflicts when Claude Code is active"
            },
            self.STANDALONE_ENV: {
                "protocol": "auto",
                "preferredProtocol": "mcp",
                "fallbackEnabled": True,
                "reason": "Standalone environment - MCP preferred for performance",
                "log_title": "📋 Protocol Configuration: Auto (MCP preferred)",
                "log_reason": "MCP provides best performance in standalone scenarios"
            }
        }

        # Get configuration for environment type (default to standalone if unknown)
        config = config_map.get(env_type, config_map[self.STANDALONE_ENV])

        # Log the configuration
        self.info(config["log_title"])
        self.info(f"   Reason: {config['log_reason']}")

        # Return only the protocol configuration (excluding logging fields)
        return {
            "protocol": config["protocol"],
            "preferredProtocol": config["preferredProtocol"],
            "fallbackEnabled": config["fallbackEnabled"],
            "reason": config["reason"]
        }

    def validate_mcp_prerequisites(self, detected_config: Optional[Dict] = None) -> Tuple[bool, List[str]]:
        """Validate that MCP memory service is properly configured."""
        issues = []

        if not detected_config:
            detected_config = self.detect_claude_mcp_configuration()

        if not detected_config:
            issues.append("No memory server found in Claude Code MCP configuration")
            return False, issues

        # Check if server is connected
        status = detected_config.get('status', '')
        if '✓ Connected' not in status and 'Connected' not in status:
            issues.append(f"Memory server is not connected. Status: {status}")

        # Validate command format
        command = detected_config.get('command', '')
        if not command:
            issues.append("Memory server command is empty")
        elif 'mcp_memory_service' not in command:
            issues.append(f"Unexpected memory server command: {command}")

        # Check server type
        server_type = detected_config.get('type', '')
        if server_type not in ['stdio', 'http']:
            issues.append(f"Unsupported server type: {server_type}")

        return len(issues) == 0, issues

    def generate_hooks_config_from_mcp(self, detected_config: Dict, env_type: str = "standalone") -> Dict:
        """Generate hooks configuration based on detected Claude Code MCP setup.

        Args:
            detected_config: Dictionary containing detected MCP configuration
            env_type: Environment type ('claude-code' or 'standalone'), defaults to 'standalone'

        Returns:
            Dictionary containing complete hooks configuration
        """
        command = detected_config.get('command', '')
        server_type = detected_config.get('type', 'stdio')

        # Get environment-appropriate protocol configuration
        protocol_config = self.configure_protocol_for_environment(env_type)

        if server_type == 'stdio':
            # For stdio servers, we'll reference the existing server
            mcp_config = {
                "useExistingServer": True,
                "serverName": "memory",
                "connectionTimeout": 5000,
                "toolCallTimeout": 10000
            }
        else:
            # For HTTP servers, extract endpoint information
            mcp_config = {
                "useExistingServer": True,
                "serverName": "memory",
                "connectionTimeout": 5000,
                "toolCallTimeout": 10000
            }

        # Detect Python path based on platform
        python_path = self._detect_python_path()

        config = {
            "codeExecution": {
                "enabled": True,
                "timeout": 8000,
                "fallbackToMCP": True,
                "enableMetrics": True,
                "pythonPath": python_path
            },
            "memoryService": {
                "protocol": protocol_config["protocol"],
                "preferredProtocol": protocol_config["preferredProtocol"],
                "fallbackEnabled": protocol_config["fallbackEnabled"],
                "http": {
                    "endpoint": "https://localhost:8443",
                    "apiKey": "auto-detect",
                    "healthCheckTimeout": 3000,
                    "useDetailedHealthCheck": True
                },
                "mcp": mcp_config,
                "defaultTags": ["claude-code", "auto-generated"],
                "maxMemoriesPerSession": 8,
                "enableSessionConsolidation": True,
                "injectAfterCompacting": False,
                "recentFirstMode": True,
                "recentMemoryRatio": 0.6,
                "recentTimeWindow": "last-week",
                "fallbackTimeWindow": "last-month",
                "showStorageSource": True,
                "sourceDisplayMode": "brief"
            }
        }

        return config

    def generate_basic_config(self, env_type: str = "standalone") -> Dict:
        """Generate basic configuration when no template is available.

        Args:
            env_type: Environment type ('claude-code' or 'standalone'), defaults to 'standalone'

        Returns:
            Dictionary containing basic hooks configuration
        """
        # Get environment-appropriate protocol configuration
        protocol_config = self.configure_protocol_for_environment(env_type)

        # Detect Python path based on platform
        python_path = self._detect_python_path()

        return {
            "codeExecution": {
                "enabled": True,
                "timeout": 8000,
                "fallbackToMCP": True,
                "enableMetrics": True,
                "pythonPath": python_path
            },
            "memoryService": {
                "protocol": protocol_config["protocol"],
                "preferredProtocol": protocol_config["preferredProtocol"],
                "fallbackEnabled": protocol_config["fallbackEnabled"],
                "http": {
                    "endpoint": "https://localhost:8443",
                    "apiKey": "auto-detect",
                    "healthCheckTimeout": 3000,
                    "useDetailedHealthCheck": True
                },
                "mcp": {
                    "serverCommand": ["uv", "run", "python", "-m", "mcp_memory_service.server"],
                    "serverWorkingDir": str(self.script_dir.parent),
                    "connectionTimeout": 5000,
                    "toolCallTimeout": 10000
                },
                "defaultTags": ["claude-code", "auto-generated"],
                "maxMemoriesPerSession": 8,
                "enableSessionConsolidation": True,
                "injectAfterCompacting": False,
                "recentFirstMode": True,
                "recentMemoryRatio": 0.6,
                "recentTimeWindow": "last-week",
                "fallbackTimeWindow": "last-month",
                "showStorageSource": True,
                "sourceDisplayMode": "brief"
            },
            "projectDetection": {
                "gitRepository": True,
                "packageFiles": ["package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml"],
                "frameworkDetection": True,
                "languageDetection": True,
                "confidenceThreshold": 0.3
            },
            "output": {
                "verbose": True,
                "showMemoryDetails": True,
                "showProjectDetails": True,
                "cleanMode": False
            }
        }

    def enhance_config_for_natural_triggers(self, config: Dict) -> Dict:
        """Enhance configuration with Natural Memory Triggers settings."""
        # Add natural triggers configuration
        config["naturalTriggers"] = {
            "enabled": True,
            "triggerThreshold": 0.6,
            "cooldownPeriod": 30000,
            "maxMemoriesPerTrigger": 5
        }

        # Add performance configuration
        config["performance"] = {
            "defaultProfile": "balanced",
            "enableMonitoring": True,
            "autoAdjust": True,
            "profiles": {
                "speed_focused": {
                    "maxLatency": 100,
                    "enabledTiers": ["instant"],
                    "backgroundProcessing": False,
                    "degradeThreshold": 200,
                    "description": "Fastest response, minimal memory awareness"
                },
                "balanced": {
                    "maxLatency": 200,
                    "enabledTiers": ["instant", "fast"],
                    "backgroundProcessing": True,
                    "degradeThreshold": 400,
                    "description": "Moderate latency, smart memory triggers"
                },
                "memory_aware": {
                    "maxLatency": 500,
                    "enabledTiers": ["instant", "fast", "intensive"],
                    "backgroundProcessing": True,
                    "degradeThreshold": 1000,
                    "description": "Full memory awareness, accept higher latency"
                }
            }
        }

        # Add other advanced settings
        config["gitAnalysis"] = {
            "enabled": True,
            "commitLookback": 14,
            "maxCommits": 20,
            "includeChangelog": True,
            "maxGitMemories": 3,
            "gitContextWeight": 1.2
        }

        return config

    def create_backup(self) -> None:
        """Create backup of existing hooks installation."""
        if not self.claude_hooks_dir.exists():
            self.info("No existing hooks installation found - no backup needed")
            return

        timestamp = subprocess.run(['date', '+%Y%m%d-%H%M%S'],
                                 capture_output=True, text=True).stdout.strip()
        if not timestamp:  # Fallback for Windows
            import datetime
            timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')

        self.backup_dir = self.claude_hooks_dir.parent / f"hooks-backup-{timestamp}"

        try:
            shutil.copytree(self.claude_hooks_dir, self.backup_dir)
            self.success(f"Backup created: {self.backup_dir}")
        except Exception as e:
            self.warn(f"Failed to create backup: {e}")
            self.warn("Continuing without backup...")

    def install_basic_hooks(self) -> bool:
        """Install basic memory awareness hooks."""
        self.info("Installing basic memory awareness hooks...")

        try:
            # Create necessary directories
            (self.claude_hooks_dir / "core").mkdir(parents=True, exist_ok=True)
            (self.claude_hooks_dir / "utilities").mkdir(parents=True, exist_ok=True)
            (self.claude_hooks_dir / "tests").mkdir(parents=True, exist_ok=True)

            # Core hooks
            core_files = [
                "session-start.js",
                "session-end.js",
                "memory-retrieval.js",
                "topic-change.js"
            ]

            for file in core_files:
                src = self.script_dir / "core" / file
                dst = self.claude_hooks_dir / "core" / file
                if src.exists():
                    shutil.copy2(src, dst)
                else:
                    self.warn(f"Core file not found: {file}")

            # Copy ALL utility files to ensure updates are deployed
            # This prevents stale versions when files are updated in the repo
            utilities_dir = self.script_dir / "utilities"
            if utilities_dir.exists():
                utility_count = 0
                for utility_file in utilities_dir.glob("*.js"):
                    dst = self.claude_hooks_dir / "utilities" / utility_file.name
                    shutil.copy2(utility_file, dst)
                    utility_count += 1
                self.success(f"Copied {utility_count} utility files")
            else:
                self.warn("Utilities directory not found")

            # Tests
            test_files = ["integration-test.js"]
            for file in test_files:
                src = self.script_dir / "tests" / file
                dst = self.claude_hooks_dir / "tests" / file
                if src.exists():
                    shutil.copy2(src, dst)

            # Documentation
            readme_src = self.script_dir / "README.md"
            if readme_src.exists():
                shutil.copy2(readme_src, self.claude_hooks_dir / "README.md")

            # StatusLine script (v8.5.7+)
            statusline_src = self.script_dir / "statusline.sh"
            if statusline_src.exists():
                statusline_dst = self.claude_hooks_dir / "statusline.sh"
                shutil.copy2(statusline_src, statusline_dst)
                # Make executable on Unix-like systems
                if self.platform_name != 'windows':
                    os.chmod(statusline_dst, 0o755)
                self.success("StatusLine script installed")

                # Check for jq dependency
                jq_available = shutil.which('jq') is not None
                if jq_available:
                    self.success("✓ jq is installed (required for statusLine)")
                else:
                    self.warn("⚠ jq not found - statusLine requires jq for JSON parsing")
                    self.info("  Install jq:")
                    if self.platform_name == 'darwin':
                        self.info("    macOS: brew install jq")
                    elif self.platform_name == 'linux':
                        self.info("    Linux: sudo apt install jq  (or equivalent)")
                    elif self.platform_name == 'windows':
                        self.info("    Windows: choco install jq  (or download from https://jqlang.github.io/jq/)")

            self.success("Basic hooks installed successfully")
            return True

        except Exception as e:
            self.error(f"Failed to install basic hooks: {e}")
            return False

    def install_auto_capture(self) -> bool:
        """Install Smart Auto-Capture hooks for automatic memory capture."""
        self.info("Installing Smart Auto-Capture hooks...")

        try:
            # Ensure directories exist
            (self.claude_hooks_dir / "core").mkdir(parents=True, exist_ok=True)
            (self.claude_hooks_dir / "utilities").mkdir(parents=True, exist_ok=True)

            # Auto-capture pattern definitions (shared utility)
            patterns_src = self.script_dir / "utilities" / "auto-capture-patterns.js"
            if patterns_src.exists():
                shutil.copy2(patterns_src, self.claude_hooks_dir / "utilities" / "auto-capture-patterns.js")
                self.success("Installed auto-capture-patterns.js")
            else:
                self.warn("auto-capture-patterns.js not found")
                return False

            # Auto-capture hook (Node.js version - primary)
            hook_js_src = self.script_dir / "core" / "auto-capture-hook.js"
            if hook_js_src.exists():
                shutil.copy2(hook_js_src, self.claude_hooks_dir / "core" / "auto-capture-hook.js")
                self.success("Installed auto-capture-hook.js (Node.js)")
            else:
                self.warn("auto-capture-hook.js not found")
                return False

            # Auto-capture hook (PowerShell version - Windows alternative)
            hook_ps1_src = self.script_dir / "core" / "auto-capture-hook.ps1"
            if hook_ps1_src.exists():
                shutil.copy2(hook_ps1_src, self.claude_hooks_dir / "core" / "auto-capture-hook.ps1")
                self.success("Installed auto-capture-hook.ps1 (PowerShell)")
            else:
                self.warn("auto-capture-hook.ps1 not found (optional for Windows)")

            self.success("Smart Auto-Capture hooks installed successfully")
            return True

        except Exception as e:
            self.error(f"Failed to install Smart Auto-Capture hooks: {e}")
            return False

    def install_natural_triggers(self) -> bool:
        """Install Natural Memory Triggers v7.1.3 components."""
        self.info("Installing Natural Memory Triggers v7.1.3...")

        try:
            # Ensure directories exist
            (self.claude_hooks_dir / "core").mkdir(parents=True, exist_ok=True)
            (self.claude_hooks_dir / "utilities").mkdir(parents=True, exist_ok=True)

            # Mid-conversation hook
            mid_conv_src = self.script_dir / "core" / "mid-conversation.js"
            if mid_conv_src.exists():
                shutil.copy2(mid_conv_src, self.claude_hooks_dir / "core" / "mid-conversation.js")
                self.success("Installed mid-conversation hooks")
            else:
                self.warn("Mid-conversation hook not found")

            # CRITICAL: Copy ALL utility files to ensure updates are deployed
            # This prevents the issue where updated files like memory-scorer.js don't get copied
            utilities_dir = self.script_dir / "utilities"
            if utilities_dir.exists():
                utility_count = 0
                for utility_file in utilities_dir.glob("*.js"):
                    dst = self.claude_hooks_dir / "utilities" / utility_file.name
                    shutil.copy2(utility_file, dst)
                    utility_count += 1
                self.success(f"Copied {utility_count} utility files (ensuring all updates are deployed)")
            else:
                self.warn("Utilities directory not found")

            # CLI management tools
            cli_tools = [
                "memory-mode-controller.js",
                "debug-pattern-test.js"
            ]

            for file in cli_tools:
                src = self.script_dir / file
                dst = self.claude_hooks_dir / file
                if src.exists():
                    shutil.copy2(src, dst)

            # Test files
            test_files = [
                "test-natural-triggers.js",
                "test-mcp-hook.js",
                "test-dual-protocol-hook.js"
            ]

            for file in test_files:
                src = self.script_dir / file
                dst = self.claude_hooks_dir / file
                if src.exists():
                    shutil.copy2(src, dst)

            self.success("Natural Memory Triggers v7.1.3 installed successfully")
            return True

        except Exception as e:
            self.error(f"Failed to install Natural Memory Triggers: {e}")
            return False

    def install_configuration(self, install_natural_triggers: bool = False, detected_mcp: Optional[Dict] = None, env_type: str = "standalone") -> bool:
        """Install or update configuration files.

        Args:
            install_natural_triggers: Whether to include Natural Memory Triggers configuration
            detected_mcp: Optional detected MCP configuration to use
            env_type: Environment type ('claude-code' or 'standalone'), defaults to 'standalone'

        Returns:
            True if installation successful, False otherwise
        """
        self.info("Installing configuration...")

        try:
            # Install template configuration
            template_src = self.script_dir / "config.template.json"
            template_dst = self.claude_hooks_dir / "config.template.json"
            if template_src.exists():
                shutil.copy2(template_src, template_dst)

            # Install main configuration
            config_src = self.script_dir / "config.json"
            config_dst = self.claude_hooks_dir / "config.json"

            if config_dst.exists():
                # Backup existing config
                backup_config = config_dst.with_suffix('.json.backup')
                shutil.copy2(config_dst, backup_config)
                self.info("Existing configuration backed up")

            # Generate configuration based on detected MCP or fallback to template
            try:
                if detected_mcp:
                    # Use smart configuration generation for existing MCP
                    config = self.generate_hooks_config_from_mcp(detected_mcp, env_type)
                    self.success("Generated configuration based on detected MCP setup")
                elif config_src.exists():
                    # Use template configuration and update paths
                    with open(config_src, 'r', encoding='utf-8') as f:
                        config = json.load(f)

                    # Update server working directory path for independent setup
                    if 'memoryService' in config and 'mcp' in config['memoryService']:
                        config['memoryService']['mcp']['serverWorkingDir'] = str(self.script_dir.parent)

                    self.success("Generated configuration using template with updated paths")
                else:
                    # Generate basic configuration
                    config = self.generate_basic_config(env_type)
                    self.success("Generated basic configuration")

                # Add additional configuration based on installation options
                if install_natural_triggers:
                    config = self.enhance_config_for_natural_triggers(config)

                # Write the final configuration
                with open(config_dst, 'w', encoding='utf-8') as f:
                    json.dump(config, f, indent=2, ensure_ascii=False)

                self.success("Configuration installed successfully")

            except Exception as e:
                self.warn(f"Failed to generate configuration: {e}")
                # Fallback to template copy if available
                if config_src.exists():
                    shutil.copy2(config_src, config_dst)
                    self.warn("Fell back to template configuration")

            return True

        except Exception as e:
            self.error(f"Failed to install configuration: {e}")
            return False

    def configure_claude_settings(self, install_mid_conversation: bool = False, install_auto_capture: bool = False) -> bool:
        """Configure Claude Code settings.json for hook integration."""
        self.info("Configuring Claude Code settings...")

        try:
            # Determine settings path based on platform
            home = Path.home()
            if self.platform_name == 'windows':
                settings_dir = home / 'AppData' / 'Roaming' / 'Claude'
            else:
                settings_dir = home / '.claude'

            settings_dir.mkdir(parents=True, exist_ok=True)
            settings_file = settings_dir / 'settings.json'

            # Windows-specific warning for SessionStart hooks (issue #160)
            skip_session_start = False
            if self.platform_name == 'windows':
                self.warn("Windows Platform Detected - SessionStart Hook Limitation")
                self.warn("SessionStart hooks cause Claude Code to hang on Windows (issue #160)")
                self.warn("Workaround: Use '/session-start' slash command instead")
                self.info("Skipping SessionStart hook configuration for Windows")
                self.info("See: https://github.com/doobidoo/mcp-memory-service/issues/160")
                skip_session_start = True

            # Create hook configuration
            hook_config = {
                "hooks": {}
            }

            # Add SessionStart only on non-Windows platforms
            if not skip_session_start:
                hook_config["hooks"]["SessionStart"] = [
                    {
                        "hooks": [
                            {
                                "type": "command",
                                "command": f'node "{self.claude_hooks_dir}/core/session-start.js"',
                                "timeout": 10
                            }
                        ]
                    }
                ]

            # SessionEnd works on all platforms
            hook_config["hooks"]["SessionEnd"] = [
                        {
                            "hooks": [
                                {
                                    "type": "command",
                                    "command": f'node "{self.claude_hooks_dir}/core/session-end.js"',
                                    "timeout": 15
                                }
                            ]
                        }
                    ]

            # Add mid-conversation hook if Natural Memory Triggers are installed
            if install_mid_conversation:
                hook_config["hooks"]["UserPromptSubmit"] = [
                    {
                        "hooks": [
                            {
                                "type": "command",
                                "command": f'node "{self.claude_hooks_dir}/core/mid-conversation.js"',
                                "timeout": 8
                            }
                        ]
                    }
                ]

            # Add PostToolUse hook for auto-capture if enabled
            if install_auto_capture:
                # Use PowerShell on Windows, Node.js elsewhere
                if self.platform_name == 'windows':
                    auto_capture_command = f'powershell -ExecutionPolicy Bypass -File "{self.claude_hooks_dir}/core/auto-capture-hook.ps1"'
                else:
                    auto_capture_command = f'node "{self.claude_hooks_dir}/core/auto-capture-hook.js"'

                hook_config["hooks"]["PostToolUse"] = [
                    {
                        "matchers": ["Edit", "Write", "Bash"],
                        "hooks": [
                            {
                                "type": "command",
                                "command": auto_capture_command,
                                "timeout": 5
                            }
                        ]
                    }
                ]
                self.success("Added PostToolUse hook for Smart Auto-Capture (Edit, Write, Bash)")

            # Add statusLine configuration for v8.5.7+ (Unix/Linux/macOS only - requires bash)
            statusline_script = self.claude_hooks_dir / 'statusline.sh'
            if statusline_script.exists() and self.platform_name != 'windows':
                hook_config["statusLine"] = {
                    "type": "command",
                    "command": str(statusline_script),
                    "padding": 0
                }
                self.info("Added statusLine configuration for memory awareness display")
            elif statusline_script.exists() and self.platform_name == 'windows':
                self.info("Skipping statusLine (requires bash - not available on Windows)")

            # Handle existing settings with intelligent merging
            final_config = hook_config
            if settings_file.exists():
                # Backup existing settings
                backup_settings = settings_file.with_suffix('.json.backup')
                shutil.copy2(settings_file, backup_settings)
                self.info("Existing settings.json backed up")

                try:
                    # Load existing settings
                    with open(settings_file, 'r', encoding='utf-8') as f:
                        existing_settings = json.load(f)

                    # Intelligent merging: preserve existing hooks while adding/updating memory awareness hooks
                    if 'hooks' not in existing_settings:
                        existing_settings['hooks'] = {}

                    # Check for conflicts and merge intelligently
                    memory_hook_types = {'SessionStart', 'SessionEnd', 'UserPromptSubmit'}
                    conflicts = []

                    for hook_type in memory_hook_types:
                        if hook_type in existing_settings['hooks'] and hook_type in hook_config['hooks']:
                            # Check if existing hook is different from our memory awareness hook
                            existing_commands = [
                                hook.get('command', '') for hooks_group in existing_settings['hooks'][hook_type]
                                for hook in hooks_group.get('hooks', [])
                            ]
                            memory_commands = [
                                hook.get('command', '') for hooks_group in hook_config['hooks'][hook_type]
                                for hook in hooks_group.get('hooks', [])
                            ]

                            # Check if any existing command contains memory hook
                            is_memory_hook = any('session-start.js' in cmd or 'session-end.js' in cmd or 'mid-conversation.js' in cmd
                                               for cmd in existing_commands)

                            if not is_memory_hook:
                                conflicts.append(hook_type)

                    if conflicts:
                        self.warn(f"Found existing non-memory hooks for: {', '.join(conflicts)}")
                        self.warn("Memory awareness hooks will be added alongside existing hooks")

                        # Add memory hooks alongside existing ones
                        for hook_type in hook_config['hooks']:
                            if hook_type in existing_settings['hooks']:
                                existing_settings['hooks'][hook_type].extend(hook_config['hooks'][hook_type])
                            else:
                                existing_settings['hooks'][hook_type] = hook_config['hooks'][hook_type]
                    else:
                        # No conflicts, safe to update memory awareness hooks
                        existing_settings['hooks'].update(hook_config['hooks'])
                        self.info("Updated memory awareness hooks without conflicts")

                    final_config = existing_settings
                    self.success("Settings merged intelligently, preserving existing configuration")

                except json.JSONDecodeError as e:
                    self.warn(f"Existing settings.json invalid, using backup and creating new: {e}")
                    final_config = hook_config
                except Exception as e:
                    self.warn(f"Error merging settings, creating new configuration: {e}")
                    final_config = hook_config

            # Write final configuration
            with open(settings_file, 'w', encoding='utf-8') as f:
                json.dump(final_config, f, indent=2, ensure_ascii=False)

            self.success("Claude Code settings configured successfully")
            return True

        except Exception as e:
            self.error(f"Failed to configure Claude Code settings: {e}")
            return False

    def run_tests(self, test_natural_triggers: bool = False) -> bool:
        """Run hook tests to verify installation."""
        self.info("Running installation tests...")

        success = True

        # Check required files exist
        required_files = [
            "core/session-start.js",
            "core/session-end.js",
            "utilities/project-detector.js",
            "utilities/memory-scorer.js",
            "utilities/context-formatter.js",
            "config.json"
        ]

        if test_natural_triggers:
            required_files.extend([
                "core/mid-conversation.js",
                "utilities/adaptive-pattern-detector.js",
                "utilities/performance-manager.js",
                "utilities/mcp-client.js"
            ])

        missing_files = []
        for file in required_files:
            if not (self.claude_hooks_dir / file).exists():
                missing_files.append(file)

        if missing_files:
            self.error("Installation incomplete - missing files:")
            for file in missing_files:
                self.error(f"  - {file}")
            success = False
        else:
            self.success("All required files installed correctly")

        # Test Node.js execution
        test_script = self.claude_hooks_dir / "core" / "session-start.js"
        if test_script.exists():
            try:
                result = subprocess.run(['node', '--check', str(test_script)],
                                      capture_output=True, text=True, timeout=10)
                if result.returncode == 0:
                    self.success("Hook JavaScript syntax validation passed")
                else:
                    self.error(f"Hook JavaScript syntax validation failed: {result.stderr}")
                    success = False
            except Exception as e:
                self.warn(f"Could not validate JavaScript syntax: {e}")

        # Run integration tests if available
        integration_test = self.claude_hooks_dir / "tests" / "integration-test.js"
        if integration_test.exists():
            try:
                self.info("Running integration tests...")
                result = subprocess.run(['node', str(integration_test)],
                                      capture_output=True, text=True,
                                      timeout=30, cwd=str(self.claude_hooks_dir))
                if result.returncode == 0:
                    self.success("Integration tests passed")
                else:
                    self.warn("Some integration tests failed - check configuration")
                    if result.stdout:
                        self.info(f"Test output: {result.stdout}")
            except Exception as e:
                self.warn(f"Could not run integration tests: {e}")

        # Run Natural Memory Triggers tests if applicable
        if test_natural_triggers:
            natural_test = self.claude_hooks_dir / "test-natural-triggers.js"
            if natural_test.exists():
                try:
                    self.info("Running Natural Memory Triggers tests...")
                    result = subprocess.run(['node', str(natural_test)],
                                          capture_output=True, text=True,
                                          timeout=30, cwd=str(self.claude_hooks_dir))
                    if result.returncode == 0:
                        self.success("Natural Memory Triggers tests passed")
                    else:
                        self.warn("Some Natural Memory Triggers tests failed")
                except Exception as e:
                    self.warn(f"Could not run Natural Memory Triggers tests: {e}")

        return success

    def _cleanup_empty_directories(self) -> None:
        """Remove empty directories after uninstall."""
        try:
            # Directories to check for cleanup (in reverse order to handle nested structure)
            directories_to_check = [
                self.claude_hooks_dir / "core",
                self.claude_hooks_dir / "utilities",
                self.claude_hooks_dir / "tests"
            ]

            for directory in directories_to_check:
                if directory.exists() and directory.is_dir():
                    try:
                        # Check if directory is empty (no files, only empty subdirectories allowed)
                        items = list(directory.iterdir())
                        if not items:
                            # Directory is completely empty
                            directory.rmdir()
                            self.info(f"Removed empty directory: {directory.name}/")
                        else:
                            # Check if it only contains empty subdirectories
                            all_empty = True
                            for item in items:
                                if item.is_file():
                                    all_empty = False
                                    break
                                elif item.is_dir() and list(item.iterdir()):
                                    all_empty = False
                                    break

                            if all_empty:
                                # Remove empty subdirectories first
                                for item in items:
                                    if item.is_dir():
                                        item.rmdir()
                                # Then remove the parent directory
                                directory.rmdir()
                                self.info(f"Removed empty directory tree: {directory.name}/")
                    except OSError:
                        # Directory not empty or permission issue, skip silently
                        pass

        except Exception as e:
            self.warn(f"Could not cleanup empty directories: {e}")

    def uninstall(self) -> bool:
        """Remove installed hooks."""
        self.info("Uninstalling Claude Code memory awareness hooks...")

        try:
            if not self.claude_hooks_dir.exists():
                self.info("No hooks installation found")
                return True

            # Remove hook files
            files_to_remove = [
                "core/session-start.js",
                "core/session-end.js",
                "core/mid-conversation.js",
                "core/memory-retrieval.js",
                "core/topic-change.js",
                "memory-mode-controller.js",
                "test-natural-triggers.js",
                "test-mcp-hook.js",
                "debug-pattern-test.js"
            ]

            # Remove utilities
            utility_files = [
                "utilities/adaptive-pattern-detector.js",
                "utilities/performance-manager.js",
                "utilities/mcp-client.js",
                "utilities/memory-client.js",
                "utilities/tiered-conversation-monitor.js"
            ]
            files_to_remove.extend(utility_files)

            removed_count = 0
            for file in files_to_remove:
                file_path = self.claude_hooks_dir / file
                if file_path.exists():
                    file_path.unlink()
                    removed_count += 1

            # Remove config files if user confirms
            config_file = self.claude_hooks_dir / "config.json"
            if config_file.exists():
                # We'll keep config files by default since they may have user customizations
                self.info("Configuration files preserved (contains user customizations)")

            # Clean up empty directories
            self._cleanup_empty_directories()

            self.success(f"Removed {removed_count} hook files and cleaned up empty directories")
            return True

        except Exception as e:
            self.error(f"Failed to uninstall hooks: {e}")
            return False


def main():
    """Main installer function."""
    parser = argparse.ArgumentParser(
        description="Unified Claude Code Memory Awareness Hooks Installer",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python install_hooks.py                    # Install all features (default)
  python install_hooks.py --basic            # Basic hooks only
  python install_hooks.py --natural-triggers # Natural Memory Triggers only
  python install_hooks.py --auto-capture     # Smart Auto-Capture only
  python install_hooks.py --test             # Run tests only
  python install_hooks.py --uninstall        # Remove hooks

Features:
  Basic: Session-start and session-end hooks for memory awareness
  Natural Triggers: v7.1.3 intelligent automatic memory awareness with
                   pattern detection, performance optimization, and CLI tools
  Auto-Capture: Intelligent automatic memory capture after Edit/Write/Bash
                operations with pattern detection (Decision/Error/Learning/etc.)
        """
    )

    parser.add_argument('--basic', action='store_true',
                        help='Install basic memory awareness hooks only')
    parser.add_argument('--natural-triggers', action='store_true',
                        help='Install Natural Memory Triggers v7.1.3 only')
    parser.add_argument('--auto-capture', action='store_true',
                        help='Install Smart Auto-Capture hooks only')
    parser.add_argument('--all', action='store_true',
                        help='Install all features (default behavior)')
    parser.add_argument('--test', action='store_true',
                        help='Run tests only (do not install)')
    parser.add_argument('--uninstall', action='store_true',
                        help='Remove installed hooks')
    parser.add_argument('--force', action='store_true',
                        help='Force installation even if prerequisites fail')
    parser.add_argument('--dry-run', action='store_true',
                        help='Show what would be installed without making changes')

    args = parser.parse_args()

    # Create installer instance
    installer = HookInstaller()

    installer.header(f"Claude Code Memory Awareness Hooks Installer v{get_project_version()}")
    installer.info(f"Script location: {installer.script_dir}")
    installer.info(f"Target hooks directory: {installer.claude_hooks_dir}")
    installer.info(f"Platform: {installer.platform_name}")

    # Handle special modes first
    if args.uninstall:
        if installer.uninstall():
            installer.success("Hooks uninstalled successfully")
        else:
            installer.error("Uninstall failed")
            sys.exit(1)
        return

    if args.test:
        test_natural_triggers = not args.basic
        if installer.run_tests(test_natural_triggers=test_natural_triggers):
            installer.success("All tests passed")
        else:
            installer.error("Some tests failed")
            sys.exit(1)
        return

    # Check prerequisites
    if not installer.check_prerequisites() and not args.force:
        installer.error("Prerequisites check failed. Use --force to continue anyway.")
        sys.exit(1)

    # Enhanced MCP Detection and Configuration
    installer.header("MCP Configuration Detection")
    detected_mcp = installer.detect_claude_mcp_configuration()

    use_existing_mcp = False
    if detected_mcp:
        # Validate MCP prerequisites
        is_valid, issues = installer.validate_mcp_prerequisites(detected_mcp)

        if is_valid:
            installer.success("✅ Valid MCP configuration detected!")
            installer.info("📋 Configuration Options:")
            installer.info("  [1] Use existing MCP setup (recommended) - DRY principle ✨")
            installer.info("  [2] Create independent hooks setup - legacy fallback")

            # For now, we'll default to using existing MCP (can be made interactive later)
            use_existing_mcp = True
            installer.info("Using existing MCP configuration (option 1)")
        else:
            installer.warn("⚠️  MCP configuration found but has issues:")
            for issue in issues:
                installer.warn(f"    - {issue}")
            installer.info("Will use independent setup as fallback")
    else:
        installer.info("No existing MCP configuration found - using independent setup")

    # Environment Detection and Protocol Configuration
    installer.header("Environment Detection & Protocol Configuration")
    env_type = installer.detect_environment_type()

    # Determine what to install
    install_all = not (args.basic or args.natural_triggers or args.auto_capture) or args.all
    install_basic = args.basic or install_all
    install_natural_triggers = args.natural_triggers or install_all
    install_auto_capture = args.auto_capture or install_all

    installer.info(f"Installation plan:")
    installer.info(f"  Basic hooks: {'Yes' if install_basic else 'No'}")
    installer.info(f"  Natural Memory Triggers: {'Yes' if install_natural_triggers else 'No'}")
    installer.info(f"  Smart Auto-Capture: {'Yes' if install_auto_capture else 'No'}")

    if args.dry_run:
        installer.info("DRY RUN - No changes will be made")
        installer.info("Would install:")
        if install_basic:
            installer.info("  - Basic memory awareness hooks")
            installer.info("  - Core utilities and configuration")
        if install_natural_triggers:
            installer.info("  - Natural Memory Triggers v7.1.3")
            installer.info("  - Mid-conversation hooks")
            installer.info("  - Performance optimization utilities")
            installer.info("  - CLI management tools")
        if install_auto_capture:
            installer.info("  - Smart Auto-Capture hooks")
            installer.info("  - Pattern detection for Decision/Error/Learning/Implementation")
            installer.info("  - PostToolUse hook (Edit, Write, Bash)")
        return

    # Create backup
    installer.create_backup()

    # Perform installation
    overall_success = True

    # Install components based on selection
    if install_basic:
        if not installer.install_basic_hooks():
            overall_success = False

    if install_natural_triggers:
        if not installer.install_natural_triggers():
            overall_success = False

    if install_auto_capture:
        if not installer.install_auto_capture():
            overall_success = False

    # Install configuration (always needed) with MCP awareness
    if not installer.install_configuration(install_natural_triggers=install_natural_triggers,
                                         detected_mcp=detected_mcp if use_existing_mcp else None,
                                         env_type=env_type):
        overall_success = False

    # Configure Claude Code settings
    if not installer.configure_claude_settings(install_mid_conversation=install_natural_triggers,
                                              install_auto_capture=install_auto_capture):
        overall_success = False

    # Run tests to verify installation
    if overall_success:
        installer.info("Running post-installation tests...")
        if installer.run_tests(test_natural_triggers=install_natural_triggers):
            installer.header("Installation Complete!")

            if install_basic and install_natural_triggers and install_auto_capture:
                installer.success("Complete Claude Code memory awareness system installed")
                installer.info("Features available:")
                installer.info("  ✅ Session-start and session-end hooks")
                installer.info("  ✅ Natural Memory Triggers with intelligent pattern detection")
                installer.info("  ✅ Mid-conversation memory injection")
                installer.info("  ✅ Smart Auto-Capture (PostToolUse for Edit/Write/Bash)")
                installer.info("  ✅ Performance optimization and CLI management")
                installer.info("")
                installer.info("CLI Management:")
                installer.info(f"  node {installer.claude_hooks_dir}/memory-mode-controller.js status")
                installer.info(f"  node {installer.claude_hooks_dir}/memory-mode-controller.js profile balanced")
                installer.info("")
                installer.info("Auto-Capture User Overrides:")
                installer.info("  #remember - Force capture this conversation")
                installer.info("  #skip     - Skip auto-capture for this message")
            elif install_basic and install_natural_triggers:
                installer.success("Memory awareness system installed (without auto-capture)")
                installer.info("Features available:")
                installer.info("  ✅ Session-start and session-end hooks")
                installer.info("  ✅ Natural Memory Triggers with intelligent pattern detection")
                installer.info("  ✅ Mid-conversation memory injection")
                installer.info("  ✅ Performance optimization and CLI management")
            elif install_auto_capture:
                installer.success("Smart Auto-Capture hooks installed")
                installer.info("Auto-capture enabled for Edit/Write/Bash operations")
                installer.info("Pattern detection: Decision/Error/Learning/Implementation/Important/Code")
                installer.info("")
                installer.info("User Overrides:")
                installer.info("  #remember - Force capture this conversation")
                installer.info("  #skip     - Skip auto-capture for this message")
            elif install_natural_triggers:
                installer.success("Natural Memory Triggers v7.1.3 installed")
                installer.info("Advanced memory awareness features available")
            elif install_basic:
                installer.success("Basic memory awareness hooks installed")
                installer.info("Session-based memory awareness enabled")

            # Code execution enabled message (applies to all installation types)
            installer.info("")
            installer.success("Code Execution Interface enabled by default")
            installer.info("  ✅ 75-90% token reduction")
            installer.info("  ✅ Automatic MCP fallback")
            installer.info("  ✅ Zero breaking changes")
            installer.info("  ℹ️  Disable in ~/.claude/hooks/config.json if needed")

        else:
            installer.warn("Installation completed but some tests failed")
            installer.info("Hooks may still work - check configuration manually")
    else:
        installer.error("Installation failed - some components could not be installed")
        sys.exit(1)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print(f"\n{Colors.YELLOW}Installation cancelled by user{Colors.NC}")
        sys.exit(1)
    except Exception as e:
        print(f"\n{Colors.RED}Unexpected error: {e}{Colors.NC}")
        sys.exit(1)