"""
CLI for zwarm orchestration.

Commands:
- zwarm orchestrate: Start an orchestrator session
- zwarm exec: Run a single executor directly (for testing)
- zwarm status: Show current state
- zwarm history: Show event history
- zwarm configs: Manage configurations
"""

from __future__ import annotations

import asyncio
import os
import sys
from enum import Enum
from pathlib import Path
from typing import Annotated, Optional

import typer
from rich import print as rprint
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text

# Create console for rich output
console = Console()


def _resolve_task(task: str | None, task_file: Path | None) -> str | None:
    """
    Resolve task from multiple sources (priority order):
    1. --task flag
    2. --task-file flag
    3. stdin (if not a tty)
    """
    # Direct task takes priority
    if task:
        return task

    # Then file
    if task_file:
        if not task_file.exists():
            console.print(f"[red]Error:[/] Task file not found: {task_file}")
            raise typer.Exit(1)
        return task_file.read_text().strip()

    # Finally stdin (only if piped, not interactive)
    if not sys.stdin.isatty():
        stdin_content = sys.stdin.read().strip()
        if stdin_content:
            return stdin_content

    return None

# Main app with rich help
app = typer.Typer(
    name="zwarm",
    help="""
[bold cyan]zwarm[/] - Multi-Agent CLI Orchestration Research Platform

[bold]DESCRIPTION[/]
    Orchestrate multiple CLI coding agents (Codex, Claude Code) with
    delegation, conversation, and trajectory alignment (watchers).

[bold]QUICK START[/]
    [dim]# Initialize zwarm in your project[/]
    $ zwarm init

    [dim]# Run the orchestrator[/]
    $ zwarm orchestrate --task "Build a hello world function"

    [dim]# Check state after running[/]
    $ zwarm status

[bold]COMMANDS[/]
    [cyan]init[/]         Initialize zwarm (create config.toml, .zwarm/)
    [cyan]reset[/]        Reset state and optionally config files
    [cyan]orchestrate[/]  Start orchestrator to delegate tasks to executors
    [cyan]exec[/]         Run a single executor directly (for testing)
    [cyan]status[/]       Show current state (sessions, tasks, events)
    [cyan]history[/]      Show event history log
    [cyan]configs[/]      Manage configuration files

[bold]CONFIGURATION[/]
    Create [cyan]config.toml[/] or use [cyan]--config[/] flag with YAML files.
    See [cyan]zwarm configs list[/] for available configurations.

[bold]ADAPTERS[/]
    [cyan]codex_mcp[/]    Codex via MCP server (sync conversations)
    [cyan]claude_code[/]  Claude Code CLI

[bold]WATCHERS[/] (trajectory aligners)
    [cyan]progress[/]     Detects stuck/spinning agents
    [cyan]budget[/]       Monitors step/session limits
    [cyan]scope[/]        Detects scope creep
    [cyan]pattern[/]      Custom regex pattern matching
    [cyan]quality[/]      Code quality checks
    """,
    rich_markup_mode="rich",
    no_args_is_help=True,
    add_completion=False,
)

# Configs subcommand group
configs_app = typer.Typer(
    name="configs",
    help="""
Manage zwarm configurations.

[bold]SUBCOMMANDS[/]
    [cyan]list[/]   List available configuration files
    [cyan]show[/]   Display a configuration file's contents
    """,
    rich_markup_mode="rich",
    no_args_is_help=True,
)
app.add_typer(configs_app, name="configs")


class AdapterType(str, Enum):
    codex_mcp = "codex_mcp"
    claude_code = "claude_code"


class ModeType(str, Enum):
    sync = "sync"
    async_ = "async"


@app.command()
def orchestrate(
    task: Annotated[Optional[str], typer.Option("--task", "-t", help="The task to accomplish")] = None,
    task_file: Annotated[Optional[Path], typer.Option("--task-file", "-f", help="Read task from file")] = None,
    config: Annotated[Optional[Path], typer.Option("--config", "-c", help="Path to config YAML")] = None,
    overrides: Annotated[Optional[list[str]], typer.Option("--set", help="Override config (key=value)")] = None,
    working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
    resume: Annotated[bool, typer.Option("--resume", help="Resume from previous state")] = False,
    max_steps: Annotated[Optional[int], typer.Option("--max-steps", help="Maximum orchestrator steps")] = None,
    verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Show detailed output")] = False,
):
    """
    Start an orchestrator session.

    The orchestrator breaks down tasks and delegates to executor agents
    (Codex, Claude Code). It can have sync conversations or fire-and-forget
    async delegations.

    [bold]Examples:[/]
        [dim]# Simple task[/]
        $ zwarm orchestrate --task "Add a logout button to the navbar"

        [dim]# Task from file[/]
        $ zwarm orchestrate -f task.md

        [dim]# Task from stdin[/]
        $ cat task.md | zwarm orchestrate
        $ zwarm orchestrate < task.md

        [dim]# With config file[/]
        $ zwarm orchestrate -c configs/base.yaml --task "Refactor auth"

        [dim]# Override settings[/]
        $ zwarm orchestrate --task "Fix bug" --set executor.adapter=claude_code

        [dim]# Resume interrupted session[/]
        $ zwarm orchestrate --task "Continue work" --resume
    """
    from zwarm.orchestrator import build_orchestrator

    # Resolve task from: --task, --task-file, or stdin
    resolved_task = _resolve_task(task, task_file)
    if not resolved_task:
        console.print("[red]Error:[/] No task provided. Use --task, --task-file, or pipe from stdin.")
        raise typer.Exit(1)

    task = resolved_task

    # Build overrides list
    override_list = list(overrides or [])
    if max_steps:
        override_list.append(f"orchestrator.max_steps={max_steps}")

    console.print(f"[bold]Starting orchestrator...[/]")
    console.print(f"  Task: {task}")
    console.print(f"  Working dir: {working_dir.absolute()}")
    console.print()

    # Output handler to show orchestrator messages
    def output_handler(msg: str) -> None:
        if msg.strip():
            console.print(f"[dim][orchestrator][/] {msg}")

    orchestrator = None
    try:
        orchestrator = build_orchestrator(
            config_path=config,
            task=task,
            working_dir=working_dir.absolute(),
            overrides=override_list,
            resume=resume,
            output_handler=output_handler,
        )

        if resume:
            console.print("  [dim]Resuming from previous state...[/]")

        # Run the orchestrator loop
        console.print("[bold]--- Orchestrator running ---[/]\n")
        result = orchestrator.run(task=task)

        console.print(f"\n[bold green]--- Orchestrator finished ---[/]")
        console.print(f"  Steps: {result.get('steps', 'unknown')}")

        # Show exit message if any
        exit_msg = getattr(orchestrator, "_exit_message", "")
        if exit_msg:
            console.print(f"  Exit: {exit_msg[:200]}")

        # Save state for potential resume
        orchestrator.save_state()

    except KeyboardInterrupt:
        console.print("\n\n[yellow]Interrupted.[/]")
        if orchestrator:
            orchestrator.save_state()
            console.print("[dim]State saved. Use --resume to continue.[/]")
        sys.exit(1)
    except Exception as e:
        console.print(f"\n[red]Error:[/] {e}")
        if verbose:
            console.print_exception()
        sys.exit(1)


@app.command()
def exec(
    task: Annotated[str, typer.Option("--task", "-t", help="Task to execute")],
    adapter: Annotated[AdapterType, typer.Option("--adapter", "-a", help="Executor adapter")] = AdapterType.codex_mcp,
    mode: Annotated[ModeType, typer.Option("--mode", "-m", help="Execution mode")] = ModeType.sync,
    working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
    model: Annotated[Optional[str], typer.Option("--model", help="Model override")] = None,
):
    """
    Run a single executor directly (for testing).

    Useful for testing adapters without the full orchestrator loop.

    [bold]Examples:[/]
        [dim]# Test Codex[/]
        $ zwarm exec --task "What is 2+2?"

        [dim]# Test Claude Code[/]
        $ zwarm exec -a claude_code --task "List files in current dir"

        [dim]# Async mode[/]
        $ zwarm exec --task "Build feature" --mode async
    """
    from zwarm.adapters.codex_mcp import CodexMCPAdapter
    from zwarm.adapters.claude_code import ClaudeCodeAdapter

    console.print(f"[bold]Running executor directly...[/]")
    console.print(f"  Adapter: [cyan]{adapter.value}[/]")
    console.print(f"  Mode: {mode.value}")
    console.print(f"  Task: {task}")

    if adapter == AdapterType.codex_mcp:
        executor = CodexMCPAdapter(model=model)
    elif adapter == AdapterType.claude_code:
        executor = ClaudeCodeAdapter(model=model)
    else:
        console.print(f"[red]Unknown adapter:[/] {adapter}")
        sys.exit(1)

    async def run():
        try:
            session = await executor.start_session(
                task=task,
                working_dir=working_dir.absolute(),
                mode=mode.value,
                model=model,
            )

            console.print(f"\n[green]Session started:[/] {session.id[:8]}")

            if mode == ModeType.sync:
                response = session.messages[-1].content if session.messages else "(no response)"
                console.print(f"\n[bold]Response:[/]\n{response}")

                # Interactive loop for sync mode
                while True:
                    try:
                        user_input = console.input("\n[dim]> (type message or 'exit')[/] ")
                        if user_input.lower() == "exit" or not user_input:
                            break

                        response = await executor.send_message(session, user_input)
                        console.print(f"\n[bold]Response:[/]\n{response}")
                    except KeyboardInterrupt:
                        break
            else:
                console.print("[dim]Async mode - session running in background.[/]")
                console.print("Use 'zwarm status' to check progress.")

        finally:
            await executor.cleanup()

    asyncio.run(run())


@app.command()
def status(
    working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
):
    """
    Show current state (sessions, tasks, events).

    Displays active sessions, pending tasks, and recent events
    from the .zwarm state directory.

    [bold]Example:[/]
        $ zwarm status
    """
    from zwarm.core.state import StateManager

    state_dir = working_dir / ".zwarm"
    if not state_dir.exists():
        console.print("[yellow]No zwarm state found in this directory.[/]")
        console.print("[dim]Run 'zwarm orchestrate' to start.[/]")
        return

    state = StateManager(state_dir)
    state.load()

    # Sessions table
    sessions = state.list_sessions()
    console.print(f"\n[bold]Sessions[/] ({len(sessions)})")
    if sessions:
        table = Table(show_header=True, header_style="bold")
        table.add_column("ID", style="dim")
        table.add_column("Mode")
        table.add_column("Status")
        table.add_column("Task")

        for s in sessions:
            status_style = {"active": "green", "completed": "blue", "failed": "red"}.get(s.status.value, "white")
            table.add_row(
                s.id[:8],
                s.mode.value,
                f"[{status_style}]{s.status.value}[/]",
                s.task_description[:50] + "..." if len(s.task_description) > 50 else s.task_description,
            )
        console.print(table)
    else:
        console.print("  [dim](none)[/]")

    # Tasks table
    tasks = state.list_tasks()
    console.print(f"\n[bold]Tasks[/] ({len(tasks)})")
    if tasks:
        table = Table(show_header=True, header_style="bold")
        table.add_column("ID", style="dim")
        table.add_column("Status")
        table.add_column("Description")

        for t in tasks:
            status_style = {"pending": "yellow", "in_progress": "cyan", "completed": "green", "failed": "red"}.get(t.status.value, "white")
            table.add_row(
                t.id[:8],
                f"[{status_style}]{t.status.value}[/]",
                t.description[:50] + "..." if len(t.description) > 50 else t.description,
            )
        console.print(table)
    else:
        console.print("  [dim](none)[/]")

    # Recent events
    events = state.get_events(limit=5)
    console.print(f"\n[bold]Recent Events[/]")
    if events:
        for e in events:
            console.print(f"  [dim]{e.timestamp.strftime('%H:%M:%S')}[/] {e.kind}")
    else:
        console.print("  [dim](none)[/]")


@app.command()
def history(
    working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
    kind: Annotated[Optional[str], typer.Option("--kind", "-k", help="Filter by event kind")] = None,
    limit: Annotated[int, typer.Option("--limit", "-n", help="Number of events")] = 20,
):
    """
    Show event history.

    Displays the append-only event log with timestamps and details.

    [bold]Examples:[/]
        [dim]# Show last 20 events[/]
        $ zwarm history

        [dim]# Show more events[/]
        $ zwarm history --limit 50

        [dim]# Filter by kind[/]
        $ zwarm history --kind session_started
    """
    from zwarm.core.state import StateManager

    state_dir = working_dir / ".zwarm"
    if not state_dir.exists():
        console.print("[yellow]No zwarm state found.[/]")
        return

    state = StateManager(state_dir)
    events = state.get_events(kind=kind, limit=limit)

    console.print(f"\n[bold]Event History[/] (last {limit})\n")

    if not events:
        console.print("[dim]No events found.[/]")
        return

    table = Table(show_header=True, header_style="bold")
    table.add_column("Time", style="dim")
    table.add_column("Event")
    table.add_column("Session/Task")
    table.add_column("Details")

    for e in events:
        details = ""
        if e.payload:
            details = ", ".join(f"{k}={str(v)[:30]}" for k, v in list(e.payload.items())[:2])

        table.add_row(
            e.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
            e.kind,
            (e.session_id or e.task_id or "-")[:8],
            details[:60],
        )

    console.print(table)


@configs_app.command("list")
def configs_list(
    config_dir: Annotated[Optional[Path], typer.Option("--dir", "-d", help="Directory to search")] = None,
):
    """
    List available agent/experiment configuration files (YAML).

    Note: config.toml is for user environment settings and is loaded
    automatically - use YAML files for agent configurations.

    [bold]Example:[/]
        $ zwarm configs list
    """
    search_dirs = [
        config_dir or Path.cwd(),
        Path.cwd() / "configs",
        Path.cwd() / ".zwarm",
    ]

    console.print("\n[bold]Available Configurations[/]\n")
    found = False

    for d in search_dirs:
        if not d.exists():
            continue
        for pattern in ["*.yaml", "*.yml"]:
            for f in d.glob(pattern):
                found = True
                try:
                    rel = f.relative_to(Path.cwd())
                    console.print(f"  [cyan]{rel}[/]")
                except ValueError:
                    console.print(f"  [cyan]{f}[/]")

    if not found:
        console.print("  [dim]No configuration files found.[/]")
        console.print("\n  [dim]Create a YAML config in configs/ to get started.[/]")

    # Check for config.toml and mention it
    config_toml = Path.cwd() / "config.toml"
    if config_toml.exists():
        console.print(f"\n[dim]Environment: config.toml (loaded automatically)[/]")


@configs_app.command("show")
def configs_show(
    config_path: Annotated[Path, typer.Argument(help="Path to configuration file")],
):
    """
    Show a configuration file's contents.

    Loads and displays the resolved configuration including
    any inherited values from 'extends:' directives.

    [bold]Example:[/]
        $ zwarm configs show configs/base.yaml
    """
    from zwarm.core.config import load_config
    import json

    if not config_path.exists():
        console.print(f"[red]File not found:[/] {config_path}")
        raise typer.Exit(1)

    try:
        config = load_config(config_path=config_path)
        console.print(f"\n[bold]Configuration:[/] {config_path}\n")
        console.print_json(json.dumps(config.to_dict(), indent=2))
    except Exception as e:
        console.print(f"[red]Error loading config:[/] {e}")
        raise typer.Exit(1)


@app.command()
def init(
    working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
    non_interactive: Annotated[bool, typer.Option("--yes", "-y", help="Accept defaults, no prompts")] = False,
    with_project: Annotated[bool, typer.Option("--with-project", help="Create zwarm.yaml project config")] = False,
):
    """
    Initialize zwarm in the current directory.

    Creates configuration files and the .zwarm state directory.
    Run this once per project to set up zwarm.

    [bold]Creates:[/]
        [cyan]config.toml[/]   Runtime settings (weave, adapter, watchers)
        [cyan].zwarm/[/]       State directory for sessions and events
        [cyan]zwarm.yaml[/]    Project config (optional, with --with-project)

    [bold]Examples:[/]
        [dim]# Interactive setup[/]
        $ zwarm init

        [dim]# Quick setup with defaults[/]
        $ zwarm init --yes

        [dim]# Full setup with project config[/]
        $ zwarm init --with-project
    """
    console.print("\n[bold cyan]zwarm init[/] - Initialize zwarm configuration\n")

    config_toml_path = working_dir / "config.toml"
    zwarm_yaml_path = working_dir / "zwarm.yaml"
    state_dir = working_dir / ".zwarm"

    # Check for existing files
    if config_toml_path.exists():
        console.print(f"[yellow]Warning:[/] config.toml already exists")
        if not non_interactive:
            overwrite = typer.confirm("Overwrite?", default=False)
            if not overwrite:
                console.print("[dim]Skipping config.toml[/]")
                config_toml_path = None
        else:
            config_toml_path = None

    # Gather settings
    weave_project = ""
    adapter = "codex_mcp"
    watchers_enabled = ["progress", "budget", "delegation"]
    create_project_config = with_project
    project_description = ""
    project_context = ""

    if not non_interactive:
        console.print("[bold]Configuration[/]\n")

        # Weave project
        weave_project = typer.prompt(
            "  Weave project (entity/project, blank to skip)",
            default="",
            show_default=False,
        )

        # Adapter
        adapter = typer.prompt(
            "  Default adapter",
            default="codex_mcp",
            type=str,
        )

        # Watchers
        console.print("\n  [bold]Watchers[/] (trajectory aligners)")
        available_watchers = ["progress", "budget", "delegation", "scope", "pattern", "quality"]
        watchers_enabled = []
        for w in available_watchers:
            default = w in ["progress", "budget", "delegation"]
            if typer.confirm(f"    Enable {w}?", default=default):
                watchers_enabled.append(w)

        # Project config
        console.print()
        create_project_config = typer.confirm(
            "  Create zwarm.yaml project config?",
            default=with_project,
        )

        if create_project_config:
            project_description = typer.prompt(
                "    Project description",
                default="",
                show_default=False,
            )
            console.print("    [dim]Project context (optional, press Enter twice to finish):[/]")
            context_lines = []
            while True:
                line = typer.prompt("    ", default="", show_default=False)
                if not line:
                    break
                context_lines.append(line)
            project_context = "\n".join(context_lines)

    # Create .zwarm directory
    console.print("\n[bold]Creating files...[/]\n")

    state_dir.mkdir(parents=True, exist_ok=True)
    (state_dir / "sessions").mkdir(exist_ok=True)
    (state_dir / "orchestrator").mkdir(exist_ok=True)
    console.print(f"  [green]✓[/] Created .zwarm/")

    # Create config.toml
    if config_toml_path:
        toml_content = _generate_config_toml(
            weave_project=weave_project,
            adapter=adapter,
            watchers=watchers_enabled,
        )
        config_toml_path.write_text(toml_content)
        console.print(f"  [green]✓[/] Created config.toml")

    # Create zwarm.yaml
    if create_project_config:
        if zwarm_yaml_path.exists() and not non_interactive:
            overwrite = typer.confirm("  zwarm.yaml exists. Overwrite?", default=False)
            if not overwrite:
                create_project_config = False

        if create_project_config:
            yaml_content = _generate_zwarm_yaml(
                description=project_description,
                context=project_context,
                watchers=watchers_enabled,
            )
            zwarm_yaml_path.write_text(yaml_content)
            console.print(f"  [green]✓[/] Created zwarm.yaml")

    # Summary
    console.print("\n[bold green]Done![/] zwarm is ready.\n")
    console.print("[bold]Next steps:[/]")
    console.print("  [dim]# Run the orchestrator[/]")
    console.print("  $ zwarm orchestrate --task \"Your task here\"\n")
    console.print("  [dim]# Or test an executor directly[/]")
    console.print("  $ zwarm exec --task \"What is 2+2?\"\n")


def _generate_config_toml(
    weave_project: str = "",
    adapter: str = "codex_mcp",
    watchers: list[str] | None = None,
) -> str:
    """Generate config.toml content."""
    watchers = watchers or []

    lines = [
        "# zwarm configuration",
        "# Generated by 'zwarm init'",
        "",
        "[weave]",
    ]

    if weave_project:
        lines.append(f'project = "{weave_project}"')
    else:
        lines.append("# project = \"your-entity/your-project\"  # Uncomment to enable Weave tracing")

    lines.extend([
        "",
        "[orchestrator]",
        "max_steps = 50",
        "",
        "[executor]",
        f'adapter = "{adapter}"',
        "# model = \"\"  # Optional model override",
        "",
        "[watchers]",
        f"enabled = {watchers}",
        "",
        "# Watcher-specific configuration",
        "# [watchers.budget]",
        "# max_steps = 50",
        "# warn_at_percent = 80",
        "",
        "# [watchers.pattern]",
        "# patterns = [\"DROP TABLE\", \"rm -rf\"]",
        "",
    ])

    return "\n".join(lines)


def _generate_zwarm_yaml(
    description: str = "",
    context: str = "",
    watchers: list[str] | None = None,
) -> str:
    """Generate zwarm.yaml project config."""
    watchers = watchers or []

    lines = [
        "# zwarm project configuration",
        "# Customize the orchestrator for this specific project",
        "",
        f'description: "{description}"' if description else 'description: ""',
        "",
        "# Project-specific context injected into the orchestrator",
        "# This helps the orchestrator understand your codebase",
        "context: |",
    ]

    if context:
        for line in context.split("\n"):
            lines.append(f"  {line}")
    else:
        lines.extend([
            "  # Describe your project here. For example:",
            "  # - Tech stack (FastAPI, React, PostgreSQL)",
            "  # - Key directories (src/api/, src/components/)",
            "  # - Coding conventions to follow",
        ])

    lines.extend([
        "",
        "# Project-specific constraints",
        "# The orchestrator will be reminded to follow these",
        "constraints:",
        "  # - \"Never modify migration files directly\"",
        "  # - \"All new endpoints need tests\"",
        "  # - \"Use existing patterns from src/api/\"",
        "",
        "# Default watchers for this project",
        "watchers:",
    ])

    for w in watchers:
        lines.append(f"  - {w}")

    if not watchers:
        lines.append("  # - progress")
        lines.append("  # - budget")

    lines.append("")

    return "\n".join(lines)


@app.command()
def reset(
    working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
    state: Annotated[bool, typer.Option("--state", "-s", help="Reset .zwarm/ state directory")] = True,
    config: Annotated[bool, typer.Option("--config", "-c", help="Also delete config.toml")] = False,
    project: Annotated[bool, typer.Option("--project", "-p", help="Also delete zwarm.yaml")] = False,
    all_files: Annotated[bool, typer.Option("--all", "-a", help="Delete everything (state + config + project)")] = False,
    force: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False,
):
    """
    Reset zwarm state and optionally configuration files.

    By default, only clears the .zwarm/ state directory (sessions, events, orchestrator history).
    Use flags to also remove configuration files.

    [bold]Examples:[/]
        [dim]# Reset state only (default)[/]
        $ zwarm reset

        [dim]# Reset everything, no confirmation[/]
        $ zwarm reset --all --yes

        [dim]# Reset state and config.toml[/]
        $ zwarm reset --config
    """
    import shutil

    console.print("\n[bold cyan]zwarm reset[/] - Reset zwarm state\n")

    state_dir = working_dir / ".zwarm"
    config_toml_path = working_dir / "config.toml"
    zwarm_yaml_path = working_dir / "zwarm.yaml"

    # Expand --all flag
    if all_files:
        state = True
        config = True
        project = True

    # Collect what will be deleted
    to_delete = []
    if state and state_dir.exists():
        to_delete.append((".zwarm/", state_dir))
    if config and config_toml_path.exists():
        to_delete.append(("config.toml", config_toml_path))
    if project and zwarm_yaml_path.exists():
        to_delete.append(("zwarm.yaml", zwarm_yaml_path))

    if not to_delete:
        console.print("[yellow]Nothing to reset.[/] No matching files found.")
        raise typer.Exit(0)

    # Show what will be deleted
    console.print("[bold]Will delete:[/]")
    for name, path in to_delete:
        if path.is_dir():
            # Count contents
            files = list(path.rglob("*"))
            file_count = len([f for f in files if f.is_file()])
            console.print(f"  [red]✗[/] {name} ({file_count} files)")
        else:
            console.print(f"  [red]✗[/] {name}")

    # Confirm
    if not force:
        console.print()
        confirm = typer.confirm("Proceed with reset?", default=False)
        if not confirm:
            console.print("[dim]Aborted.[/]")
            raise typer.Exit(0)

    # Delete
    console.print("\n[bold]Deleting...[/]")
    for name, path in to_delete:
        try:
            if path.is_dir():
                shutil.rmtree(path)
            else:
                path.unlink()
            console.print(f"  [green]✓[/] Deleted {name}")
        except Exception as e:
            console.print(f"  [red]✗[/] Failed to delete {name}: {e}")

    console.print("\n[bold green]Reset complete.[/]")
    console.print("\n[dim]Run 'zwarm init' to set up again.[/]\n")


@app.command()
def clean(
    force: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False,
    dry_run: Annotated[bool, typer.Option("--dry-run", "-n", help="Show what would be killed without killing")] = False,
):
    """
    Clean up orphaned processes from zwarm sessions.

    Finds and kills:
    - Orphaned codex mcp-server processes
    - Orphaned codex exec processes
    - Orphaned claude CLI processes

    [bold]Examples:[/]
        [dim]# See what would be cleaned[/]
        $ zwarm clean --dry-run

        [dim]# Clean without confirmation[/]
        $ zwarm clean --yes
    """
    import subprocess
    import signal

    console.print("\n[bold cyan]zwarm clean[/] - Clean up orphaned processes\n")

    # Patterns to search for
    patterns = [
        ("codex mcp-server", "Codex MCP server"),
        ("codex exec", "Codex exec"),
        ("claude.*--permission-mode", "Claude CLI"),
    ]

    found_processes = []

    for pattern, description in patterns:
        try:
            # Use pgrep to find matching processes
            result = subprocess.run(
                ["pgrep", "-f", pattern],
                capture_output=True,
                text=True,
            )
            if result.returncode == 0 and result.stdout.strip():
                pids = result.stdout.strip().split("\n")
                for pid in pids:
                    pid = pid.strip()
                    if pid and pid.isdigit():
                        # Get process info
                        try:
                            ps_result = subprocess.run(
                                ["ps", "-p", pid, "-o", "pid,ppid,etime,command"],
                                capture_output=True,
                                text=True,
                            )
                            if ps_result.returncode == 0:
                                lines = ps_result.stdout.strip().split("\n")
                                if len(lines) > 1:
                                    # Skip header, get process line
                                    proc_info = lines[1].strip()
                                    found_processes.append((int(pid), description, proc_info))
                        except Exception:
                            found_processes.append((int(pid), description, "(unknown)"))
        except FileNotFoundError:
            # pgrep not available, try ps with grep
            try:
                result = subprocess.run(
                    f"ps aux | grep '{pattern}' | grep -v grep",
                    shell=True,
                    capture_output=True,
                    text=True,
                )
                if result.returncode == 0 and result.stdout.strip():
                    for line in result.stdout.strip().split("\n"):
                        parts = line.split()
                        if len(parts) >= 2:
                            pid = parts[1]
                            if pid.isdigit():
                                found_processes.append((int(pid), description, line[:80]))
            except Exception:
                pass
        except Exception as e:
            console.print(f"[yellow]Warning:[/] Error searching for {description}: {e}")

    if not found_processes:
        console.print("[green]No orphaned processes found.[/] Nothing to clean.\n")
        raise typer.Exit(0)

    # Show what was found
    console.print(f"[bold]Found {len(found_processes)} process(es):[/]\n")
    for pid, description, info in found_processes:
        console.print(f"  [yellow]PID {pid}[/] - {description}")
        console.print(f"    [dim]{info[:100]}{'...' if len(info) > 100 else ''}[/]")

    if dry_run:
        console.print("\n[dim]Dry run - no processes killed.[/]\n")
        raise typer.Exit(0)

    # Confirm
    if not force:
        console.print()
        confirm = typer.confirm(f"Kill {len(found_processes)} process(es)?", default=False)
        if not confirm:
            console.print("[dim]Aborted.[/]")
            raise typer.Exit(0)

    # Kill processes
    console.print("\n[bold]Cleaning up...[/]")
    killed = 0
    failed = 0

    for pid, description, _ in found_processes:
        try:
            # First try SIGTERM
            os.kill(pid, signal.SIGTERM)
            console.print(f"  [green]✓[/] Killed PID {pid} ({description})")
            killed += 1
        except ProcessLookupError:
            console.print(f"  [dim]○[/] PID {pid} already gone")
        except PermissionError:
            console.print(f"  [red]✗[/] PID {pid} - permission denied (try sudo)")
            failed += 1
        except Exception as e:
            console.print(f"  [red]✗[/] PID {pid} - {e}")
            failed += 1

    console.print(f"\n[bold green]Cleanup complete.[/] Killed {killed}, failed {failed}.\n")


@app.callback(invoke_without_command=True)
def main_callback(
    ctx: typer.Context,
    version: Annotated[bool, typer.Option("--version", "-V", help="Show version")] = False,
):
    """Main callback for version flag."""
    if version:
        console.print("[bold cyan]zwarm[/] version [green]0.1.0[/]")
        raise typer.Exit()


def main():
    """Entry point for the CLI."""
    app()


if __name__ == "__main__":
    main()
