"""X-IPE CLI main entry point."""
import json
import click
from pathlib import Path
from typing import Optional

from ..core.config import XIPEConfig
from ..core.scaffold import ScaffoldManager
from ..core.skills import SkillsManager
from .. import __version__
from ..services.cli_adapter_service import CLIAdapterService
from ..services.mcp_deployer_service import MCPDeployerService


@click.group(invoke_without_command=True)
@click.version_option(version=__version__, prog_name="x-ipe")
@click.option(
    "--project", "-p",
    type=click.Path(exists=False, file_okay=False, dir_okay=True, path_type=Path),
    default=None,
    help="Project root directory. Defaults to current directory.",
)
@click.pass_context
def cli(ctx: click.Context, project: Optional[Path]) -> None:
    """X-IPE: AI-powered development framework.
    
    Manage X-IPE projects from the command line.
    """
    ctx.ensure_object(dict)
    
    # Resolve project root
    project_root = (project or Path.cwd()).resolve()
    ctx.obj["project_root"] = project_root
    
    # Try to load config
    try:
        config = XIPEConfig.load(project_root)
    except Exception:
        config = XIPEConfig.defaults(project_root)
    
    ctx.obj["config"] = config
    
    # Show help if no command specified
    if ctx.invoked_subcommand is None:
        click.echo(ctx.get_help())


@cli.command()
@click.pass_context
def status(ctx: click.Context) -> None:
    """Show project status.
    
    Display current X-IPE project status including:
    - Configuration status
    - Skill counts
    - Project structure status
    
    Exit codes:
    - 0: Project is initialized
    - 1: Project is not initialized
    """
    project_root = ctx.obj["project_root"]
    config = ctx.obj["config"]
    
    click.echo(f"X-IPE Status for: {project_root}")
    click.echo("-" * 40)
    
    # Track initialization status
    is_initialized = True
    
    # Config status
    if config.config_path:
        click.echo(f"✓ Config: {config.config_path.name}")
    else:
        click.echo("○ Config: Using defaults (no .x-ipe.yaml)")
        is_initialized = False
    
    # Docs structure
    if config.docs_path.exists():
        click.echo(f"✓ Docs: {config.docs_path.relative_to(project_root)}")
    else:
        click.echo(f"○ Docs: Not initialized")
        is_initialized = False
    
    # Runtime folder
    if config.runtime_path.exists():
        click.echo(f"✓ Runtime: {config.runtime_path.relative_to(project_root)}")
    else:
        click.echo(f"○ Runtime: Not initialized")
        is_initialized = False
    
    # Skills
    skills_manager = SkillsManager(project_root)
    local_skills = skills_manager.get_local_skills()
    
    if config.skills_path.exists():
        click.echo(f"✓ Skills: {len(local_skills)} local skills")
        
        modified = skills_manager.detect_modifications()
        if modified:
            click.echo(f"  ⚠ {len(modified)} modified from package")
    else:
        click.echo(f"○ Skills: Not initialized")
        is_initialized = False
    
    # Exit with code 1 if not initialized
    if not is_initialized:
        ctx.exit(1)


@cli.command()
@click.pass_context
def info(ctx: click.Context) -> None:
    """Show X-IPE information.
    
    Display X-IPE package information including:
    - Version
    - Package location
    - Available skills
    """
    project_root = ctx.obj["project_root"]
    config = ctx.obj["config"]
    
    click.echo(f"X-IPE v{__version__}")
    click.echo("-" * 40)
    
    # Package info - use relative import
    from .. import __file__ as pkg_file
    click.echo(f"Package: {Path(pkg_file).parent}")
    
    # Config info
    click.echo(f"\nProject: {project_root}")
    click.echo(f"Config file: {config.config_path or 'None (using defaults)'}")
    
    # Paths
    click.echo(f"\nPaths:")
    click.echo(f"  docs: {config.docs_path}")
    click.echo(f"  skills: {config.skills_path}")
    click.echo(f"  runtime: {config.runtime_path}")
    
    # Server settings
    click.echo(f"\nServer:")
    click.echo(f"  host: {config.server_host}")
    click.echo(f"  port: {config.server_port}")
    click.echo(f"  debug: {config.server_debug}")
    
    # Skill summary
    skills_manager = SkillsManager(project_root)
    package_skills = skills_manager.get_package_skills()
    local_skills = skills_manager.get_local_skills()
    
    click.echo(f"\nSkills:")
    click.echo(f"  package: {len(package_skills)}")
    click.echo(f"  local: {len(local_skills)}")
    
    if local_skills:
        click.echo(f"\nLocal skills:")
        for skill in local_skills[:10]:  # Show max 10
            status = "✓" if not skill.modified else "⚠"
            click.echo(f"  {status} {skill.name}")
        if len(local_skills) > 10:
            click.echo(f"  ... and {len(local_skills) - 10} more")


@cli.command()
@click.option(
    "--force", "-f",
    is_flag=True,
    help="Overwrite existing files.",
)
@click.option(
    "--dry-run", "-n",
    is_flag=True,
    help="Show what would be done without making changes.",
)
@click.option(
    "--no-skills",
    is_flag=True,
    help="Skip copying skills from package.",
)
@click.option(
    "--no-mcp",
    is_flag=True,
    help="Skip MCP config merge prompt.",
)
@click.option(
    "--cli", "cli_name",
    default=None,
    help="CLI to use (copilot, opencode, claude-code). Skips interactive prompt.",
)
@click.option(
    "--lang", "lang",
    default=None,
    help="Language for instructions (en, zh). Skips interactive prompt.",
)
@click.pass_context
def init(ctx: click.Context, force: bool, dry_run: bool, no_skills: bool, no_mcp: bool, cli_name: Optional[str], lang: Optional[str]) -> None:
    """Initialize X-IPE in the current project.
    
    Creates the standard X-IPE folder structure:
    - x-ipe-docs/ with subfolders for planning, requirements, features
    - .x-ipe/ for runtime data (database, cache)
    - .github/skills/ with bundled skills from package
    - .x-ipe.yaml configuration file
    """
    project_root = ctx.obj["project_root"]
    
    if dry_run:
        click.echo(f"Dry run - previewing changes for: {project_root}")
    else:
        click.echo(f"Initializing X-IPE in: {project_root}")
    
    click.echo("-" * 40)
    
    # CLI Detection & Selection (FEATURE-027-B)
    selected_cli = _resolve_cli_selection(project_root, cli_name)
    
    # Language Selection (FEATURE-028-B)
    selected_lang = _resolve_language_selection(lang)
    
    # Create scaffold manager
    scaffold = ScaffoldManager(project_root, dry_run=dry_run, force=force)
    
    # Create folder structure
    scaffold.create_docs_structure()
    
    # Copy skills if requested
    if not no_skills:
        scaffold.copy_skills(cli_name=selected_cli)
    
    # Copy/merge instructions file
    scaffold.copy_copilot_instructions(cli_name=selected_cli, language=selected_lang)
    
    # Copy MCP config (.github/copilot/mcp-config.json) — only for copilot
    if selected_cli == 'copilot':
        scaffold.copy_mcp_config()
    
    # Copy config files (copilot-prompt.json, tools.json, .env.example)
    scaffold.copy_config_files()
    
    # Copy planning templates (features.md, task-board.md)
    scaffold.copy_planning_templates()
    
    # Copy default theme
    scaffold.copy_themes()
    
    # Create config file
    scaffold.create_config_file(cli_name=selected_cli, language=selected_lang)
    
    # MCP config merge using active CLI's target path
    # Read MCP servers from package resources so it works for all CLIs
    mcp_servers = {}
    _mcp_res = scaffold._get_resource_path("copilot")
    if _mcp_res:
        _mcp_file = _mcp_res / "mcp-config.json"
        if _mcp_file.exists():
            try:
                mcp_servers = json.loads(_mcp_file.read_text()).get("mcpServers", {})
            except (json.JSONDecodeError, IOError):
                pass
    deployer = MCPDeployerService(project_root)
    if mcp_servers and not dry_run and not no_mcp:
        click.echo("\n" + "-" * 40)
        click.echo("MCP Server Configuration")
        click.echo("-" * 40)
        
        # Show available servers
        click.echo(f"\nFound {len(mcp_servers)} MCP server(s) in project config:")
        for name in mcp_servers:
            click.echo(f"  • {name}")
        
        # Confirm each server
        servers_to_merge = []
        for name in mcp_servers:
            if click.confirm(f"\nAdd '{name}' to MCP config?", default=True):
                servers_to_merge.append(name)
        
        if servers_to_merge:
            mcp_format = 'project'
            try:
                adapter = CLIAdapterService().get_adapter(selected_cli)
                target_path = deployer.resolve_target_path(adapter)
                mcp_format = adapter.mcp_config_format
            except Exception:
                target_path = project_root / ".copilot" / "mcp-config.json"
            
            target_path = click.prompt(
                "\nTarget MCP config path",
                default=str(target_path),
                type=click.Path(dir_okay=False, path_type=Path)
            )
            
            # Use deployer with explicit target override via scaffold for backward compat
            scaffold.merge_mcp_config(
                servers_to_merge=servers_to_merge,
                target_path=target_path,
                source_servers=mcp_servers,
                mcp_format=mcp_format
            )
            click.echo(f"\n✓ Merged {len(servers_to_merge)} MCP server(s) to {target_path}")
    
    # Show summary
    created, skipped = scaffold.get_summary()
    
    if dry_run:
        click.echo("\nWould create:")
    else:
        click.echo("\nCreated:")
    
    for path in created:
        try:
            rel_path = path.relative_to(project_root)
        except ValueError:
            rel_path = path
        click.echo(f"  ✓ {rel_path}")
    
    if skipped:
        click.echo("\nSkipped (already exists):")
        for path in skipped:
            try:
                rel_path = path.relative_to(project_root)
            except ValueError:
                rel_path = path
            click.echo(f"  ○ {rel_path}")
    
    if dry_run:
        click.echo("\nRun without --dry-run to apply changes.")
    else:
        click.echo(f"\n✓ X-IPE initialized in {project_root}")


def _handle_cli_migration(project_root: Path, cli_name: str, dry_run: bool, force: bool) -> None:
    """Handle CLI migration when --cli is provided on upgrade.
    
    FEATURE-027-E: CLI Migration & Upgrade
    """
    import shutil
    import yaml
    from datetime import datetime

    # Validate CLI name
    try:
        service = CLIAdapterService()
    except Exception:
        click.echo("Error: CLI adapter service unavailable.")
        raise click.Abort()

    adapter_names = [a.name for a in service.list_adapters()]
    if cli_name not in adapter_names:
        available = ', '.join(adapter_names)
        raise click.BadParameter(
            f"Unknown CLI '{cli_name}'. Available: {available}",
            param_hint="'--cli'",
        )

    # Read current CLI from config
    config_path = project_root / '.x-ipe.yaml'
    if not config_path.exists():
        click.echo("Error: No .x-ipe.yaml found. Run `x-ipe init` first.")
        raise click.Abort()

    with open(config_path) as f:
        config = yaml.safe_load(f) or {}
    current_cli = config.get('cli', 'copilot')

    if current_cli == cli_name and not force:
        click.echo(f"Already using '{cli_name}'. No migration needed.")
        click.echo("Use --force to re-run migration (e.g. to copy missing skills).")
        return

    same_cli = (current_cli == cli_name)

    click.echo(f"Migrating {'(re-run)' if same_cli else f'from {current_cli!r}'} to '{cli_name}'")
    click.echo("-" * 40)

    old_adapter = service.get_adapter(current_cli) if not same_cli else None
    new_adapter = service.get_adapter(cli_name)

    # 1. Backup old artifacts (skip when re-running same CLI)
    old_skills = project_root / old_adapter.skills_folder if old_adapter else None
    old_instructions = project_root / old_adapter.instructions_file if old_adapter else None

    if not same_cli:
        timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
        backup_dir = project_root / ".x-ipe" / "backup" / f"{current_cli}-{timestamp}"
        backed_up = []

        if not dry_run:
            backup_dir.mkdir(parents=True, exist_ok=True)

            if old_skills and old_skills.exists():
                shutil.copytree(old_skills, backup_dir / "skills", dirs_exist_ok=True)
                backed_up.append(str(old_skills))

            if old_instructions and old_instructions.exists():
                shutil.copy2(old_instructions, backup_dir / old_instructions.name)
                backed_up.append(str(old_instructions))

            # Back up project-scoped or nested MCP config
            if old_adapter.mcp_config_format in ("project", "nested"):
                old_mcp = project_root / old_adapter.mcp_config_path
                if old_mcp.exists():
                    shutil.copy2(old_mcp, backup_dir / old_mcp.name)
                    backed_up.append(str(old_mcp))

        if backed_up:
            click.echo(f"Backed up {len(backed_up)} artifact(s) to {backup_dir}")
        elif not dry_run:
            click.echo("No artifacts to back up.")

    # 2. Update .x-ipe.yaml
    if not dry_run:
        config['cli'] = cli_name
        # Update skills path to match new CLI adapter
        if 'paths' not in config:
            config['paths'] = {}
        config['paths']['skills'] = new_adapter.skills_folder.rstrip('/')
        with open(config_path, 'w') as f:
            yaml.dump(config, f, default_flow_style=False)
        click.echo(f"Updated .x-ipe.yaml: cli → {cli_name}")
        click.echo(f"Updated .x-ipe.yaml: skills → {new_adapter.skills_folder}")

    # 3. Migrate skills to new CLI folder
    new_skills = project_root / new_adapter.skills_folder
    if not new_skills.exists() or (force and old_skills and old_skills.exists()):
        # Find source: explicit old adapter, or scan all known CLI skill folders
        source_skills = None
        if old_skills and old_skills.exists():
            source_skills = old_skills
            source_label = old_adapter.skills_folder
        else:
            # Scan all adapters to find where skills actually live
            for adapter in service.list_adapters():
                candidate = project_root / adapter.skills_folder
                if candidate.exists() and adapter.name != cli_name and any(candidate.iterdir()):
                    source_skills = candidate
                    source_label = adapter.skills_folder
                    break

        if source_skills:
            if not dry_run:
                new_skills.parent.mkdir(parents=True, exist_ok=True)
                shutil.copytree(source_skills, new_skills, dirs_exist_ok=True)
                click.echo(f"Copied skills: {source_label} → {new_adapter.skills_folder}")
            else:
                click.echo(f"Would copy skills: {source_label} → {new_adapter.skills_folder}")
        elif not new_skills.exists():
            click.echo(f"No source skills found to copy to {new_adapter.skills_folder}")
    else:
        click.echo(f"Skills folder already exists: {new_adapter.skills_folder} (skipped)")

    # 4. Migrate instructions file
    new_instructions = project_root / new_adapter.instructions_file
    if not new_instructions.exists() or (force and old_instructions and old_instructions.exists()):
        source_instructions = None
        if old_instructions and old_instructions.exists():
            source_instructions = old_instructions
            source_instr_label = old_adapter.instructions_file
        else:
            for adapter in service.list_adapters():
                candidate = project_root / adapter.instructions_file
                if candidate.exists() and adapter.name != cli_name:
                    source_instructions = candidate
                    source_instr_label = adapter.instructions_file
                    break

        if source_instructions:
            if not dry_run:
                new_instructions.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(source_instructions, new_instructions)
                click.echo(f"Copied instructions: {source_instr_label} → {new_adapter.instructions_file}")
            else:
                click.echo(f"Would copy instructions: {source_instr_label} → {new_adapter.instructions_file}")
        elif not new_instructions.exists():
            click.echo(f"No source instructions found to copy to {new_adapter.instructions_file}")
    else:
        click.echo(f"Instructions file already exists: {new_adapter.instructions_file} (skipped)")

    # 5. Deploy MCP config for new CLI
    deployer = MCPDeployerService(project_root)
    result = deployer.deploy(new_adapter, force=force, dry_run=dry_run)
    if result.merged_count > 0:
        click.echo(f"Deployed {result.merged_count} MCP server(s) for {cli_name}")

    # 6. Summary
    if dry_run:
        click.echo("\nDry run - no changes made.")
    else:
        click.echo(f"\n✓ Migration complete: {current_cli} → {cli_name}")


def _resolve_cli_selection(project_root: Path, cli_flag: Optional[str]) -> str:
    """Resolve CLI selection through flag, config, detection, or prompt.
    
    FEATURE-027-B: CLI Init & Selection
    
    Priority: --cli flag > existing config > auto-detection > default (copilot)
    """
    try:
        service = CLIAdapterService()
    except Exception:
        click.echo("ℹ CLI adapter service unavailable. Defaulting to copilot.")
        return 'copilot'

    adapters = service.list_adapters()
    adapter_names = [a.name for a in adapters]

    # --cli flag: validate and return immediately
    if cli_flag:
        if cli_flag not in adapter_names:
            available = ', '.join(adapter_names)
            raise click.BadParameter(
                f"Unknown CLI '{cli_flag}'. Available: {available}",
                param_hint="'--cli'",
            )
        if not service.is_installed(cli_flag):
            click.echo(f"⚠ Warning: '{cli_flag}' is not installed on this system.")
        return cli_flag

    # Detect installed CLIs
    installed = service.detect_installed_clis()

    # Determine default from existing config or detection
    existing_cli = _read_existing_cli(project_root)
    if existing_cli and existing_cli in adapter_names:
        default = existing_cli
    elif installed:
        default = installed[0]
    else:
        click.echo("ℹ No supported CLI detected. Defaulting to copilot.")
        default = 'copilot'

    # Show detected info
    if installed:
        display = ', '.join(installed)
        click.echo(f"Detected CLI(s): {display}")

    # Prompt for selection (auto-accept default if non-interactive)
    try:
        selected = click.prompt(
            "Select CLI",
            type=click.Choice(adapter_names, case_sensitive=False),
            default=default,
        )
    except (click.Abort, EOFError):
        selected = default
        click.echo(f"Using default CLI: {selected}")

    if selected not in installed:
        click.echo(f"⚠ Warning: '{selected}' is not installed on this system.")

    return selected


def _read_existing_cli(project_root: Path) -> Optional[str]:
    """Read existing cli key from .x-ipe.yaml if present."""
    config_path = project_root / '.x-ipe.yaml'
    if not config_path.exists():
        return None
    try:
        import yaml
        with open(config_path) as f:
            config = yaml.safe_load(f)
        return config.get('cli') if config else None
    except Exception:
        return None


SUPPORTED_LANGUAGES = ['en', 'zh']


def _resolve_language_selection(lang_flag: Optional[str] = None) -> str:
    """Resolve language selection from flag or interactive prompt.
    
    Args:
        lang_flag: Language code from --lang option, or None for interactive.
        
    Returns:
        Selected language code ('en' or 'zh').
    """
    if lang_flag:
        if lang_flag not in SUPPORTED_LANGUAGES:
            raise click.BadParameter(
                f"Invalid language '{lang_flag}'. Supported: {', '.join(SUPPORTED_LANGUAGES)}",
                param_hint="'--lang'",
            )
        return lang_flag

    try:
        selected = click.prompt(
            "Select language for instructions",
            type=click.Choice(SUPPORTED_LANGUAGES, case_sensitive=False),
            default='en',
        )
    except (click.Abort, EOFError):
        selected = 'en'
        click.echo("Using default language: en")

    return selected


def _handle_language_switch(project_root: Path, language: str, dry_run: bool = False) -> None:
    """Switch language: update .x-ipe.yaml and re-extract instructions.
    
    Args:
        project_root: Project root path.
        language: Target language code.
        dry_run: If True, show what would be done.
    """
    import yaml
    
    config_path = project_root / '.x-ipe.yaml'
    if not config_path.exists():
        click.echo("Error: Project not initialized. Run `x-ipe init` first.")
        return
    
    click.echo(f"Switching language to: {language}")
    
    if not dry_run:
        # Update .x-ipe.yaml
        with open(config_path) as f:
            config = yaml.safe_load(f) or {}
        config['language'] = language
        with open(config_path, 'w') as f:
            yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
        
        # Re-extract instructions
        existing_cli = config.get('cli', 'copilot')
        scaffold = ScaffoldManager(project_root, dry_run=False, force=True)
        scaffold.copy_copilot_instructions(cli_name=existing_cli, language=language)
    
    click.echo(f"✓ Language switched to '{language}'")


def _kill_port(port: int) -> None:
    """Kill any process listening on the given port."""
    import subprocess
    try:
        result = subprocess.run(
            ["lsof", "-ti", f":{port}"],
            capture_output=True, text=True, timeout=5
        )
        pids = result.stdout.strip().split('\n')
        for pid in pids:
            if pid.strip():
                subprocess.run(["kill", "-9", pid.strip()], timeout=5)
    except Exception:
        pass


@cli.command()
@click.option(
    "--host", "-h",
    default=None,
    help="Host to bind to (default: from config or 127.0.0.1).",
)
@click.option(
    "--port", "-P",  # Using -P because -p is already used for --project
    type=int,
    default=None,
    help="Port to bind to (default: from config or 5959).",
)
@click.option(
    "--debug/--no-debug",
    default=None,
    help="Enable/disable debug mode.",
)
@click.option(
    "--open", "-o",
    is_flag=True,
    help="Open browser after starting server.",
)
@click.pass_context
def serve(ctx: click.Context, host: Optional[str], port: Optional[int], 
          debug: Optional[bool], open: bool) -> None:
    """Start the X-IPE web server.
    
    Launches the Flask web server with the document viewer and
    interactive console interface.
    
    Settings are loaded from .x-ipe.yaml and can be overridden
    with command line flags.
    """
    config = ctx.obj["config"]
    project_root = ctx.obj["project_root"]
    
    # Apply config defaults, with CLI overrides
    final_host = host or config.server_host
    final_port = port or config.server_port
    final_debug = debug if debug is not None else config.server_debug
    
    click.echo(f"Starting X-IPE server for: {project_root}")
    click.echo("-" * 40)
    click.echo(f"Host: {final_host}")
    click.echo(f"Port: {final_port}")
    click.echo(f"Debug: {final_debug}")
    
    url = f"http://{final_host}:{final_port}"
    click.echo(f"\nServer URL: {url}")
    
    if open:
        import webbrowser
        click.echo("Opening browser...")
        webbrowser.open(url)
    
    # Import and run the existing Flask app
    click.echo("\nPress Ctrl+C to stop the server.")
    
    try:
        # Set environment for the app
        import os
        import signal
        os.environ["X_IPE_PROJECT_ROOT"] = str(project_root)
        os.environ["FLASK_DEBUG"] = "1" if final_debug else "0"
        
        # Kill any stale process on the port
        _kill_port(final_port)
        
        # Import the Flask app
        from x_ipe.app import create_app, socketio
        
        app = create_app()
        
        # Ensure clean shutdown on signals
        def _shutdown(signum, frame):
            click.echo("\n✓ Server stopped.")
            raise SystemExit(0)
        
        signal.signal(signal.SIGINT, _shutdown)
        signal.signal(signal.SIGTERM, _shutdown)
        
        # Run with socketio for WebSocket support
        socketio.run(
            app,
            host=final_host,
            port=final_port,
            debug=final_debug,
            use_reloader=final_debug,
            allow_unsafe_werkzeug=True,
        )
    except (KeyboardInterrupt, SystemExit):
        pass
    except ImportError as e:
        click.echo(f"\nError: Could not import app: {e}")
        click.echo("Make sure you're running from the X-IPE project root.")
        raise click.Abort()


@cli.command()
@click.option(
    "--force", "-f",
    is_flag=True,
    help="Overwrite modified skills without prompting.",
)
@click.option(
    "--dry-run", "-n",
    is_flag=True,
    help="Show what would be done without making changes.",
)
@click.option(
    "--backup/--no-backup",
    default=True,
    help="Create backups of modified skills before overwriting.",
)
@click.option(
    "--skill", "-s",
    default=None,
    help="Upgrade only the specified skill.",
)
@click.option(
    "--no-mcp",
    is_flag=True,
    help="Skip MCP config merge prompt.",
)
@click.option(
    "--cli", "cli_name",
    default=None,
    help="Switch to a different CLI adapter (triggers migration).",
)
@click.option(
    "--lang", "lang",
    default=None,
    help="Switch language for instructions (en, zh).",
)
@click.pass_context
def upgrade(ctx: click.Context, force: bool, dry_run: bool, 
            backup: bool, skill: Optional[str], no_mcp: bool, cli_name: Optional[str], lang: Optional[str]) -> None:
    """Upgrade skills from the X-IPE package.
    
    Syncs skills from the installed X-IPE package to the local
    project. Detects locally modified skills and prompts before
    overwriting (unless --force is used).
    
    Use --cli <name> to migrate to a different CLI adapter.
    """
    project_root = ctx.obj["project_root"]
    
    # CLI selection flow (FEATURE-027-E)
    # If --cli provided, use it directly; otherwise prompt like init
    if not cli_name and not force:
        existing_cli = _read_existing_cli(project_root)
        if existing_cli:
            selected_cli = _resolve_cli_selection(project_root, None)
            if selected_cli != existing_cli:
                cli_name = selected_cli

    if cli_name:
        _handle_cli_migration(project_root, cli_name, dry_run, force)
    
    # Language switch (FEATURE-028-B)
    if lang is not None:
        selected_lang = _resolve_language_selection(lang)
        _handle_language_switch(project_root, selected_lang, dry_run)
    
    click.echo(f"Upgrading X-IPE skills in: {project_root}")
    click.echo("-" * 40)
    
    # Create skills manager
    skills_manager = SkillsManager(project_root)
    
    # Check for package skills
    package_skills = skills_manager.get_package_skills()
    has_skills = bool(package_skills)
    
    if not package_skills:
        click.echo("No package skills available to sync.")
        click.echo("X-IPE may not be installed as a package, or skills are not bundled.")
    else:
        click.echo(f"Package skills available: {len(package_skills)}")
    
    # Only process skills if package skills are available
    if has_skills:
        # Check local skills
        local_skills = skills_manager.get_local_skills()
        local_skill_names = {s.name for s in local_skills}
        new_skills = [s for s in package_skills if s.name not in local_skill_names]
        
        if local_skills:
            click.echo(f"Local skills: {len(local_skills)}")
            if new_skills:
                click.echo(f"New skills available: {len(new_skills)}")
        
        # Filter by specific skill if requested
        if skill:
            package_skills = [s for s in package_skills if s.name == skill]
            if not package_skills:
                click.echo(f"\nError: Skill '{skill}' not found in package.")
                raise click.Abort()
            click.echo(f"\nUpgrading skill: {skill}")
            skill_update_choice = "all"
        elif force or dry_run:
            skill_update_choice = "all"
        else:
            # Prompt user for skill update strategy
            click.echo("\nHow would you like to update skills?")
            click.echo("  1) No - skip skill updates")
            click.echo("  2) Add new skills only (keep existing skills unchanged)")
            click.echo("  3) Update all skills (overwrite existing with package versions)")
            choice = click.prompt(
                "Choose an option",
                type=click.Choice(["1", "2", "3"]),
                default="2",
            )
            skill_update_choice = {"1": "none", "2": "new_only", "3": "all"}[choice]
        
        if skill_update_choice == "none":
            click.echo("\nSkipping skill updates.")
        elif not dry_run:
            new_only = skill_update_choice == "new_only"
            skills_to_warn = []
            
            # Warn about modified skills when updating all
            if not new_only and not force:
                modified = skills_manager.detect_modifications()
                skills_to_warn = []
                for pkg_skill in package_skills:
                    local_skill = skills_manager.get_skill_info(pkg_skill.name)
                    if local_skill and local_skill.source == "local" and local_skill.modified:
                        skills_to_warn.append(local_skill)
                
                if skills_to_warn:
                    click.echo(f"\nThe following skills have local modifications:")
                    for s in skills_to_warn:
                        click.echo(f"  ⚠ {s.name}")
                    if not click.confirm("\nOverwrite modified skills?"):
                        click.echo("Aborted skill update.")
                        new_only = None  # signal to skip
            
            if new_only is not None:
                synced = skills_manager.sync_from_package(
                    skill_name=skill,
                    backup=backup,
                    new_only=bool(new_only),
                )
                
                if synced:
                    click.echo(f"\n✓ Synced {len(synced)} skill(s):")
                    for name in synced:
                        click.echo(f"  ✓ {name}")
                else:
                    label = "new " if new_only else ""
                    click.echo(f"\nNo {label}skills to sync.")
                
                if not new_only and backup and skills_to_warn:
                    click.echo(f"\nBackups created in: {project_root / '.x-ipe' / 'backups'}")
        else:
            click.echo("\nDry run - would sync:")
            for pkg_skill in package_skills:
                if skill_update_choice == "new_only" and pkg_skill.name in local_skill_names:
                    continue
                click.echo(f"  → {pkg_skill.name}")
    
    # Copy/update MCP config from package, then merge using active CLI's path
    scaffold = ScaffoldManager(project_root, dry_run=dry_run, force=force)
    scaffold.copy_mcp_config()
    
    # MCP config merge using active CLI's target path
    deployer = MCPDeployerService(project_root)
    mcp_servers = deployer.get_source_servers()
    if mcp_servers and not dry_run and not no_mcp:
        click.echo("\n" + "-" * 40)
        click.echo("MCP Server Configuration")
        click.echo("-" * 40)
        
        # Show available servers
        click.echo(f"\nFound {len(mcp_servers)} MCP server(s) in project config:")
        for name in mcp_servers:
            click.echo(f"  • {name}")
        
        # Confirm each server (--force auto-accepts all)
        servers_to_merge = []
        for name in mcp_servers:
            if force or click.confirm(f"\nAdd '{name}' to MCP config?", default=True):
                servers_to_merge.append(name)
        
        if servers_to_merge:
            # Resolve target path from active CLI
            active_cli = _read_existing_cli(project_root) or 'copilot'
            mcp_format = 'project'
            try:
                adapter = CLIAdapterService().get_adapter(active_cli)
                target_path = deployer.resolve_target_path(adapter)
                mcp_format = adapter.mcp_config_format
            except Exception:
                target_path = project_root / ".copilot" / "mcp-config.json"
            
            if not force:
                target_path = click.prompt(
                    "\nTarget MCP config path",
                    default=str(target_path),
                    type=click.Path(dir_okay=False, path_type=Path)
                )
            
            scaffold.merge_mcp_config(
                servers_to_merge=servers_to_merge,
                target_path=target_path,
                source_servers=mcp_servers,
                mcp_format=mcp_format
            )
            
            click.echo(f"\n✓ Merged {len(servers_to_merge)} MCP server(s) to {target_path}")


def main() -> None:
    """Main entry point for the CLI."""
    cli(obj={})


if __name__ == "__main__":
    main()
