"""
AMI CLI - Main entry point for EMU management commands.

Usage:
    memrail register-emu --project project_a
    memrail validate --project project_a
    ami list --project project_a
    memrail change-state <emu_key> <state> --org acme --workspace prod --project api
    ami archive <emu_key> --org acme --workspace prod --project api
    ami archive-all --org acme --workspace prod --project api
"""
from __future__ import annotations

import os
import sys
from pathlib import Path
from typing import Optional

import typer
from dotenv import load_dotenv
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich import print as rprint

# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))

from cli.discovery import (
    discover_project_emus,
    discover_all_emus,
    find_projects_dir,
    DiscoveredEMU,
)
from cli.loader import (
    load_project_config,
    merge_emu_with_project_config,
    prepare_emu_for_registration,
)
from memrail.dsl import validate_emu, DSLSyntaxError, DSLValidationError
from memrail.tools.config import load_config, AMIToolConfig, ToolConfigError
from memrail.errors import AMIConflict, AMINotFound, AMIBadRequest

# Initialize Typer app
app = typer.Typer(
    name="ami",
    help="AMI EMU Management CLI - Register, validate, and manage EMUs",
    add_completion=False,
)

console = Console()


def load_environment() -> None:
    """Load environment variables from .env file."""
    # Try to find .env file
    env_paths = [
        Path.cwd() / ".env",
        Path.cwd().parent / ".env",
        Path(__file__).parent.parent / ".env",
    ]

    for env_path in env_paths:
        if env_path.exists():
            load_dotenv(env_path)
            return

    # No .env found, continue with system environment
    pass


def get_ami_client(
    org: Optional[str] = None,
    team: Optional[str] = None,
    org_id: Optional[str] = None,
    project_id: Optional[str] = None,
    workspace: Optional[str] = None,
    project: Optional[str] = None,
):
    """Get an AMI client instance.

    Args:
        org: Organization slug (already resolved by caller, or None to use env var)
        team: Team slug (already resolved by caller, or None to use env var)
        org_id: Organization ID (already resolved by caller, or None to use env var)
        project_id: Project ID (already resolved by caller, or None to use env var)
        workspace: Workspace slug (already resolved by caller, or None to use env var)
        project: Project slug (already resolved by caller, or None to use env var)

    Note: Callers should resolve hierarchy before calling this function.
    When a higher-level param (org) is explicitly passed, lower-level params
    (team, workspace, project) should not fall back to env vars.
    """
    from memrail import AMIClient

    api_key = os.environ.get("AMI_API_KEY")
    if not api_key:
        console.print("[red]Error:[/red] AMI_API_KEY not set. Configure in .env or environment.")
        raise typer.Exit(1)

    # Use passed values directly - callers are responsible for hierarchy resolution
    # Only fall back to env vars if no value was passed
    resolved_org = org if org is not None else os.environ.get("AMI_ORG")
    resolved_team = team if team is not None else os.environ.get("AMI_TEAM")
    resolved_org_id = org_id if org_id is not None else os.environ.get("AMI_ORG_ID")
    resolved_project_id = project_id if project_id is not None else os.environ.get("AMI_PROJECT_ID")
    resolved_workspace = workspace if workspace is not None else os.environ.get("AMI_WORKSPACE")
    resolved_project = project if project is not None else os.environ.get("AMI_PROJECT")
    resolved_base_url = os.environ.get("AMI_BASE_URL")

    return AMIClient(
        api_key=api_key,
        base_url=resolved_base_url,
        org=resolved_org,
        team=resolved_team,
        workspace=resolved_workspace,
        project=resolved_project,
        org_id=resolved_org_id,
        project_id=resolved_project_id,
    )


@app.command("register-emu")
def register(
    path: Optional[Path] = typer.Argument(
        None,
        help="File or folder path containing EMU definitions (JSON or Python files)"
    ),
    local_project: Optional[str] = typer.Option(
        None,
        "--local-project", "-l",
        help="Local project directory name in 'projects/' folder (e.g., 'project_a')"
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run", "-n",
        help="Validate without actually registering (server-side)"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Target project slug or ID (overrides AMI_PROJECT env var)"
    ),
    all_projects: bool = typer.Option(
        False,
        "--all", "-a",
        help="Register EMUs from all local projects in 'projects/' folder"
    ),
    org_id: Optional[str] = typer.Option(
        None,
        "--org-id",
        help="Organization ID (takes precedence over --org and AMI_ORG_ID env var)"
    ),
    project_id: Optional[str] = typer.Option(
        None,
        "--project-id",
        help="Project ID (takes precedence over --project and AMI_PROJECT_ID env var)"
    ),
) -> None:
    """
    Register EMUs from a file, folder, or local project directory.

    You can specify the target org, workspace, and project via CLI options
    or environment variables. CLI options always take precedence.

    Examples:
        # Register from a file or folder (positional argument)
        memrail register-emu ./emus.json --org acme --workspace prod --project api
        memrail register-emu ./emus/ -o acme -w prod -p api

        # Register from local project directory
        memrail register-emu --local-project project_a --org acme --workspace prod --project api
        memrail register-emu -l project_a -o acme -w prod -p api

        # Register all local projects
        memrail register-emu --all --org acme --workspace prod --project api

        # Dry run (validate without registering)
        memrail register-emu ./emus.json --dry-run

        # Use environment variables (AMI_ORG, AMI_WORKSPACE, AMI_PROJECT)
        memrail register-emu ./emus.json
    """
    load_environment()

    # Discover EMUs
    if path:
        from cli.discovery import load_emus_from_file, load_emus_from_json_file

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

        # Use folder name or parent folder name as project identifier
        project_name = path.name if path.is_dir() else path.parent.name

        emus = []
        errors = []

        if path.is_dir():
            # Load all JSON files from directory
            json_files = list(path.glob("*.json"))
            py_files = list(path.glob("*.py"))

            if not json_files and not py_files:
                console.print(f"[yellow]No JSON or Python files found in {path}[/yellow]")
                raise typer.Exit(0)

            if json_files:
                console.print(f"[dim]Loading {len(json_files)} JSON file(s) from {path}[/dim]")
                for json_file in json_files:
                    file_emus, file_errors = load_emus_from_json_file(json_file, project_name)
                    emus.extend(file_emus)
                    errors.extend(file_errors)

            if py_files:
                console.print(f"[dim]Loading {len(py_files)} Python file(s) from {path}[/dim]")
                for py_file in py_files:
                    if py_file.name.startswith("__"):
                        continue  # Skip __init__.py etc.
                    file_emus, file_errors = load_emus_from_file(py_file, project_name)
                    emus.extend(file_emus)
                    errors.extend(file_errors)
        elif path.suffix == ".json":
            emus, errors = load_emus_from_json_file(path, project_name)
        else:
            emus, errors = load_emus_from_file(path, project_name)

        if errors:
            for error in errors:
                console.print(f"[red]Error:[/red] {error}")
            raise typer.Exit(1)
        discovered = emus
    elif all_projects:
        result = discover_all_emus()
        if result.errors:
            for error in result.errors:
                console.print(f"[red]Error:[/red] {error}")
            raise typer.Exit(1)
        discovered = result.emus
    elif local_project:
        projects_dir = find_projects_dir()
        if not projects_dir:
            console.print("[red]Error:[/red] Could not find 'projects' directory")
            raise typer.Exit(1)

        project_path = projects_dir / local_project
        if not project_path.is_dir():
            console.print(f"[red]Error:[/red] Project directory not found: {project_path}")
            raise typer.Exit(1)

        result = discover_project_emus(project_path)
        if result.errors:
            for error in result.errors:
                console.print(f"[red]Error:[/red] {error}")
            raise typer.Exit(1)
        discovered = result.emus
    else:
        console.print("[red]Error:[/red] Specify a path, --local-project, or --all")
        console.print("\n[dim]Examples:")
        console.print("  memrail register-emu ./emus.json --org acme --workspace prod --project api")
        console.print("  memrail register-emu ./emus/ -o acme -w prod -p api")
        console.print("  memrail register-emu --local-project project_a -o acme -w prod -p api[/dim]")
        raise typer.Exit(1)

    if not discovered:
        console.print("[yellow]No EMUs found to register[/yellow]")
        raise typer.Exit(0)

    console.print(f"\n[bold]Found {len(discovered)} EMU(s) to register[/bold]\n")

    # Validate locally first
    validation_errors = []
    for emu in discovered:
        result = validate_emu(emu.definition)
        if not result.valid:
            validation_errors.append((emu, result.errors))

    if validation_errors:
        console.print("[red]Local validation failed:[/red]\n")
        for emu, errors in validation_errors:
            console.print(f"  [bold]{emu.name}[/bold] ({emu.source_file.name}):")
            for error in errors:
                console.print(f"    - {error}")
        raise typer.Exit(1)

    console.print("[green]Local validation passed[/green]\n")

    if dry_run:
        console.print("[yellow]Dry run mode - skipping registration[/yellow]")
        _display_emus_table(discovered)
        return

    # Resolve org, team, workspace, project from CLI args or env vars
    # Hierarchy: org > team > workspace > project
    # Rule: If a level is passed via CLI, use it. If not passed, use env ONLY if a lower level needs it.
    org_resolved = org or os.environ.get("AMI_ORG")
    org_id_resolved = org_id or os.environ.get("AMI_ORG_ID")
    # Team: use env if workspace or project passed, else clear if org passed
    team_resolved = os.environ.get("AMI_TEAM") if (workspace or project or project_id) else (None if org else os.environ.get("AMI_TEAM"))
    # Workspace: use CLI if passed, else use env if project passed, else clear if org passed
    workspace_resolved = workspace or (os.environ.get("AMI_WORKSPACE") if (project or project_id) else (None if org else os.environ.get("AMI_WORKSPACE")))
    # Project: use CLI if passed, else clear if any higher level passed
    project_resolved = project or (None if (org or workspace) else (project_id or os.environ.get("AMI_PROJECT")))
    project_id_resolved = project_id or (None if (org or workspace) else os.environ.get("AMI_PROJECT_ID"))

    # Validate required fields
    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Provide via --workspace or set AMI_WORKSPACE in .env")
        raise typer.Exit(1)

    if not project_resolved and not project_id_resolved:
        console.print("[red]Error:[/red] Project is required. Provide via --project or set AMI_PROJECT in .env")
        raise typer.Exit(1)

    # Register with API
    try:
        client = get_ami_client(org=org_resolved, org_id=org_id_resolved, project_id=project_id_resolved)
    except typer.Exit:
        raise

    # Show context info
    org_display = org_id_resolved or org_resolved or "?"
    console.print(f"[dim]Registering to org={org_display}, workspace={workspace_resolved}, project={project_resolved or project_id_resolved}[/dim]\n")

    success_count = 0
    error_count = 0
    results = []

    for emu in discovered:
        prepared = prepare_emu_for_registration(
            emu.definition,
            workspace=workspace_resolved,
            project=emu.project,
        )
        emu_key = prepared.get("emu_key", "?")

        try:
            response = client.register_emu(
                emu_key=prepared.get("emu_key"),
                trigger=prepared.get("trigger"),
                action=prepared.get("action"),
                expected_utility=prepared.get("expected_utility", 0.8),
                confidence=prepared.get("confidence", 0.9),
                intent=prepared.get("intent"),
                policy=prepared.get("policy"),
                state=prepared.get("state", "draft"),
                metadata=prepared.get("metadata"),
                workspace=workspace_resolved,
                project=project_resolved or project_id_resolved,
            )
            console.print(f"  [green]OK[/green] {emu_key}")
            success_count += 1
            results.append({"emu_key": emu_key, "status": "success", "response": response})
        except Exception as e:
            console.print(f"  [red]FAIL[/red] {emu_key}: {e}")
            error_count += 1
            results.append({"emu_key": emu_key, "status": "failed", "error": str(e)})

    console.print(f"\n[bold]Results:[/bold] {success_count} registered, {error_count} failed")

    if error_count > 0:
        raise typer.Exit(1)


@app.command()
def validate(
    project: str = typer.Option(
        None,
        "--project", "-p",
        help="Project directory name"
    ),
    file: Optional[Path] = typer.Option(
        None,
        "--file", "-f",
        help="Specific EMU file to validate"
    ),
    all_projects: bool = typer.Option(
        False,
        "--all", "-a",
        help="Validate EMUs from all projects"
    ),
    verbose: bool = typer.Option(
        False,
        "--verbose", "-v",
        help="Show detailed validation output"
    ),
) -> None:
    """
    Validate EMU definitions locally using DSL parser.

    This performs local validation without contacting the API,
    providing fast feedback during development.

    Examples:
        memrail validate --project project_a
        memrail validate --file path/to/emus.json
        memrail validate --file path/to/json/folder/
        memrail validate --all --verbose
    """
    load_environment()

    # Discover EMUs
    if file:
        from cli.discovery import load_emus_from_file, load_emus_from_json_file

        # Use folder name or parent folder name as project identifier
        project_name = file.name if file.is_dir() else file.parent.name

        emus = []
        errors = []

        if file.is_dir():
            # Load all JSON files from directory
            json_files = list(file.glob("*.json"))
            if not json_files:
                console.print(f"[yellow]No JSON files found in {file}[/yellow]")
                raise typer.Exit(0)
            console.print(f"[dim]Loading {len(json_files)} JSON file(s) from {file}[/dim]")
            for json_file in json_files:
                file_emus, file_errors = load_emus_from_json_file(json_file, project_name)
                emus.extend(file_emus)
                errors.extend(file_errors)
        elif file.suffix == ".json":
            emus, errors = load_emus_from_json_file(file, project_name)
        else:
            emus, errors = load_emus_from_file(file, project_name)

        if errors:
            for error in errors:
                console.print(f"[red]Error:[/red] {error}")
            raise typer.Exit(1)
        discovered = emus
    elif all_projects:
        result = discover_all_emus()
        discovered = result.emus
        for warning in result.warnings:
            console.print(f"[yellow]Warning:[/yellow] {warning}")
    elif project:
        projects_dir = find_projects_dir()
        if not projects_dir:
            console.print("[red]Error:[/red] Could not find 'projects' directory")
            raise typer.Exit(1)

        project_path = projects_dir / project
        if not project_path.is_dir():
            console.print(f"[red]Error:[/red] Project directory not found: {project_path}")
            raise typer.Exit(1)

        result = discover_project_emus(project_path)
        discovered = result.emus
        for warning in result.warnings:
            console.print(f"[yellow]Warning:[/yellow] {warning}")
    else:
        console.print("[red]Error:[/red] Specify --project, --file, or --all")
        raise typer.Exit(1)

    if not discovered:
        console.print("[yellow]No EMUs found to validate[/yellow]")
        raise typer.Exit(0)

    console.print(f"\n[bold]Validating {len(discovered)} EMU(s)[/bold]\n")

    valid_count = 0
    invalid_count = 0
    warning_count = 0

    for emu in discovered:
        result = validate_emu(emu.definition)

        if result.valid:
            if verbose:
                console.print(f"[green]Valid:[/green] {emu.name}")
            valid_count += 1
        else:
            console.print(f"[red]Invalid:[/red] {emu.name} ({emu.source_file.name})")
            for error in result.errors:
                console.print(f"  - {error}")
            invalid_count += 1

        if result.warnings:
            warning_count += len(result.warnings)
            if verbose:
                for warning in result.warnings:
                    console.print(f"[yellow]Warning:[/yellow] {emu.name}: {warning}")

    # Summary
    console.print(f"\n[bold]Results:[/bold]")
    console.print(f"  Valid: {valid_count}")
    console.print(f"  Invalid: {invalid_count}")
    if warning_count > 0:
        console.print(f"  Warnings: {warning_count}")

    if invalid_count > 0:
        raise typer.Exit(1)


@app.command("list-projects")
def list_projects(
    org: Optional[str] = typer.Option(
        None,
        "--org",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
    limit: int = typer.Option(
        100,
        "--limit", "-l",
        help="Maximum number of projects to return"
    ),
    offset: int = typer.Option(
        0,
        "--offset", "-o",
        help="Number of projects to skip (for pagination)"
    ),
) -> None:
    """
    List all projects in a workspace.

    Organization and workspace can be specified via CLI flags or env vars.
    CLI flags always take precedence over environment variables.

    Examples:
        ami list-projects --org acme-corp --workspace production
        ami list-projects -w staging --limit 10
        ami list-projects  # Uses AMI_ORG and AMI_WORKSPACE from .env
    """
    load_environment()

    # CLI arg takes precedence over env var
    # Hierarchy: org > workspace
    # If org is passed without workspace, workspace is cleared
    workspace_resolved = workspace or (None if org else (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID")))

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Provide via --workspace or set AMI_WORKSPACE in .env")
        raise typer.Exit(1)

    try:
        client = get_ami_client(org=org)
    except typer.Exit:
        raise

    # Show context info
    org_display = org or os.environ.get("AMI_ORG") or os.environ.get("AMI_ORG_ID") or "?"
    console.print(f"[dim]Fetching projects for org={org_display}, workspace={workspace_resolved}...[/dim]")

    try:
        result = client.list_projects(workspace_resolved, limit=limit, offset=offset)
    except Exception as e:
        console.print(f"[red]Error fetching projects:[/red] {e}")
        raise typer.Exit(1)

    projects = result.get("projects", [])
    total = result.get("total", len(projects))

    if not projects:
        console.print("[yellow]No projects found in workspace[/yellow]")
        raise typer.Exit(0)

    # Display projects table
    table = Table(title=f"Projects in '{workspace_resolved}'")
    table.add_column("Project ID", style="cyan")
    table.add_column("Slug", style="green")
    table.add_column("Name", style="white")
    table.add_column("Status", style="yellow")
    table.add_column("Created At", style="dim")

    for proj in projects:
        created_at = proj.get("created_at", "")
        if created_at:
            # Format datetime if it's a string
            try:
                from datetime import datetime
                if isinstance(created_at, str):
                    dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
                    created_at = dt.strftime("%Y-%m-%d %H:%M")
            except (ValueError, TypeError):
                pass

        table.add_row(
            proj.get("project_id", proj.get("id", "?")),
            proj.get("slug", "?"),
            proj.get("name", "?"),
            proj.get("status", "?"),
            str(created_at),
        )

    console.print(table)
    console.print(f"\n[dim]Showing {len(projects)} of {total} project(s)[/dim]")


@app.command("get-project")
def get_project(
    project: str = typer.Argument(
        ...,
        help="Project slug or ID to retrieve"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
) -> None:
    """
    Get details of a specific project.

    Organization and workspace can be specified via CLI flags or env vars.
    CLI flags always take precedence over environment variables.

    Examples:
        ami get-project my-project --org acme-corp --workspace production
        ami get-project prj_01KCVNE8V3FAX57QKVT9SE6K15 -w staging
        ami get-project my-project  # Uses AMI_ORG and AMI_WORKSPACE from .env
    """
    load_environment()

    # CLI arg takes precedence over env var
    # Hierarchy: org > workspace
    # If org is passed without workspace, workspace is cleared
    workspace_resolved = workspace or (None if org else (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID")))

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Provide via --workspace or set AMI_WORKSPACE in .env")
        raise typer.Exit(1)

    try:
        client = get_ami_client(org=org)
    except typer.Exit:
        raise

    # Show context info
    org_display = org or os.environ.get("AMI_ORG") or os.environ.get("AMI_ORG_ID") or "?"
    console.print(f"[dim]Fetching project: {project} (org={org_display}, workspace={workspace_resolved})...[/dim]")

    try:
        proj = client.get_project(workspace_resolved, project)
    except Exception as e:
        console.print(f"[red]Error fetching project:[/red] {e}")
        raise typer.Exit(1)

    # Format created_at
    created_at = proj.get("created_at", "")
    if created_at:
        try:
            from datetime import datetime
            if isinstance(created_at, str):
                dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
                created_at = dt.strftime("%Y-%m-%d %H:%M:%S")
        except (ValueError, TypeError):
            pass

    # Display project details
    panel_content = (
        f"[bold]Project ID:[/bold] {proj.get('project_id', proj.get('id', '?'))}\n"
        f"[bold]Slug:[/bold] {proj.get('slug', '?')}\n"
        f"[bold]Name:[/bold] {proj.get('name', '?')}\n"
        f"[bold]Status:[/bold] {proj.get('status', '?')}\n"
        f"[bold]Workspace ID:[/bold] {proj.get('workspace_id', '?')}\n"
        f"[bold]Team ID:[/bold] {proj.get('team_id', '?')}\n"
        f"[bold]Org ID:[/bold] {proj.get('org_id', '?')}\n"
        f"[bold]Created At:[/bold] {created_at}"
    )

    console.print(Panel(panel_content, title=f"Project: {proj.get('name', project)}", border_style="green"))


@app.command("create-project")
def create_project(
    slug: str = typer.Argument(
        ...,
        help="Project slug (URL-safe identifier, unique within workspace)"
    ),
    name: str = typer.Argument(
        ...,
        help="Human-readable project name"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
) -> None:
    """
    Create a new project in a workspace.

    Organization and workspace can be specified via CLI flags or env vars.
    CLI flags always take precedence over environment variables.

    Examples:
        ami create-project my-project "My Project" --org acme-corp --workspace production
        ami create-project api-backend "API Backend" -w staging
        ami create-project new-proj "New Project"  # Uses AMI_ORG and AMI_WORKSPACE from .env
    """
    load_environment()

    # CLI arg takes precedence over env var
    # Hierarchy: org > workspace
    # If org is passed without workspace, workspace is cleared
    workspace_resolved = workspace or (None if org else (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID")))

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Provide via --workspace or set AMI_WORKSPACE in .env")
        raise typer.Exit(1)

    try:
        client = get_ami_client(org=org)
    except typer.Exit:
        raise

    # Show context info
    org_display = org or os.environ.get("AMI_ORG") or os.environ.get("AMI_ORG_ID") or "?"
    console.print(f"[dim]Creating project '{slug}' in org={org_display}, workspace={workspace_resolved}...[/dim]")

    try:
        proj = client.create_project(workspace_resolved, slug, name)
    except Exception as e:
        console.print(f"[red]Error creating project:[/red] {e}")
        raise typer.Exit(1)

    # Format created_at
    created_at = proj.get("created_at", "")
    if created_at:
        try:
            from datetime import datetime
            if isinstance(created_at, str):
                dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
                created_at = dt.strftime("%Y-%m-%d %H:%M:%S")
        except (ValueError, TypeError):
            pass

    # Display project details
    panel_content = (
        f"[bold]Project ID:[/bold] {proj.get('project_id', proj.get('id', '?'))}\n"
        f"[bold]Slug:[/bold] {proj.get('slug', '?')}\n"
        f"[bold]Name:[/bold] {proj.get('name', '?')}\n"
        f"[bold]Status:[/bold] {proj.get('status', '?')}\n"
        f"[bold]Workspace ID:[/bold] {proj.get('workspace_id', '?')}\n"
        f"[bold]Team ID:[/bold] {proj.get('team_id', '?')}\n"
        f"[bold]Org ID:[/bold] {proj.get('org_id', '?')}\n"
        f"[bold]Created At:[/bold] {created_at}"
    )

    console.print(Panel(panel_content, title=f"[green]Created Project: {proj.get('name', slug)}[/green]", border_style="green"))


@app.command("list-workspaces")
def list_workspaces(
    org: Optional[str] = typer.Option(
        None,
        "--org",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    team: Optional[str] = typer.Option(
        None,
        "--team",
        help="Team slug (overrides AMI_TEAM env var)"
    ),
    limit: int = typer.Option(
        100,
        "--limit", "-l",
        help="Maximum number of workspaces to return"
    ),
    offset: int = typer.Option(
        0,
        "--offset", "-o",
        help="Number of workspaces to skip (for pagination)"
    ),
) -> None:
    """
    List all workspaces for a team.

    Organization and team can be specified via CLI flags or env vars.
    CLI flags always take precedence over environment variables.

    Examples:
        ami list-workspaces
        ami list-workspaces --org acme-corp --team engineering
        ami list-workspaces --limit 10
    """
    load_environment()

    # Hierarchy: org > team
    # If org is passed without team, team is cleared
    org_resolved = org or os.environ.get("AMI_ORG")
    team_resolved = team or (None if org else os.environ.get("AMI_TEAM"))

    try:
        client = get_ami_client(org=org_resolved, team=team_resolved)
    except typer.Exit:
        raise

    # Show context info
    org_display = org_resolved or "from API key"
    team_display = team_resolved or "from API key"
    console.print(f"[dim]Fetching workspaces for org={org_display}, team={team_display}...[/dim]")

    try:
        result = client.list_workspaces(limit=limit, offset=offset)
    except Exception as e:
        console.print(f"[red]Error fetching workspaces:[/red] {e}")
        raise typer.Exit(1)

    workspaces = result.get("workspaces", [])
    total = result.get("total", len(workspaces))

    if not workspaces:
        console.print("[yellow]No workspaces found[/yellow]")
        raise typer.Exit(0)

    # Display workspaces table
    table = Table(title="Workspaces")
    table.add_column("Workspace ID", style="cyan")
    table.add_column("Slug", style="green")
    table.add_column("Name", style="white")
    table.add_column("Status", style="yellow")
    table.add_column("Created At", style="dim")

    for ws in workspaces:
        created_at = ws.get("created_at", "")
        if created_at:
            try:
                from datetime import datetime
                if isinstance(created_at, str):
                    dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
                    created_at = dt.strftime("%Y-%m-%d %H:%M")
            except (ValueError, TypeError):
                pass

        table.add_row(
            ws.get("workspace_id", ws.get("id", "?")),
            ws.get("slug", "?"),
            ws.get("name", "?"),
            ws.get("status", "?"),
            str(created_at),
        )

    console.print(table)
    console.print(f"\n[dim]Showing {len(workspaces)} of {total} workspace(s)[/dim]")


@app.command("get-workspace")
def get_workspace(
    workspace: str = typer.Argument(
        ...,
        help="Workspace slug or ID to retrieve"
    ),
) -> None:
    """
    Get details of a specific workspace.

    Examples:
        ami get-workspace production
        ami get-workspace ws_01KCVNE8V3FAX57QKVT9SE6K15
    """
    load_environment()

    try:
        client = get_ami_client()
    except typer.Exit:
        raise

    console.print(f"[dim]Fetching workspace: {workspace}...[/dim]")

    try:
        ws = client.get_workspace(workspace)
    except Exception as e:
        console.print(f"[red]Error fetching workspace:[/red] {e}")
        raise typer.Exit(1)

    # Format created_at
    created_at = ws.get("created_at", "")
    if created_at:
        try:
            from datetime import datetime
            if isinstance(created_at, str):
                dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
                created_at = dt.strftime("%Y-%m-%d %H:%M:%S")
        except (ValueError, TypeError):
            pass

    # Display workspace details
    panel_content = (
        f"[bold]Workspace ID:[/bold] {ws.get('workspace_id', ws.get('id', '?'))}\n"
        f"[bold]Slug:[/bold] {ws.get('slug', '?')}\n"
        f"[bold]Name:[/bold] {ws.get('name', '?')}\n"
        f"[bold]Status:[/bold] {ws.get('status', '?')}\n"
        f"[bold]Org ID:[/bold] {ws.get('org_id', '?')}\n"
        f"[bold]Team ID:[/bold] {ws.get('team_id', '?')}\n"
        f"[bold]Created At:[/bold] {created_at}"
    )

    console.print(Panel(panel_content, title=f"Workspace: {ws.get('name', workspace)}", border_style="green"))


@app.command("purge-workspace")
def purge_workspace(
    workspace: Optional[str] = typer.Argument(
        None,
        help="Workspace slug to purge (defaults to AMI_WORKSPACE)"
    ),
    targets: Optional[str] = typer.Option(
        None,
        "--targets",
        help="Comma-separated targets to purge (default: all). Valid: emus, traces, events, asr, tools, cooldowns, prompts, hindsight, decision_points"
    ),
    yes: bool = typer.Option(
        False,
        "--yes", "-y",
        help="Skip confirmation prompt"
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output as raw JSON"
    ),
) -> None:
    """
    Purge all data in a workspace (EMUs, tools, events, traces, etc.)

    This is a destructive operation. Requires an org-level API key.
    The workspace itself is not deleted — only its contents.

    Examples:
        memrail purge-workspace production
        memrail purge-workspace staging --yes
        memrail purge-workspace development --targets emus,traces,events
    """
    load_environment()

    resolved = workspace or os.environ.get("AMI_WORKSPACE")
    if not resolved:
        console.print("[red]Error:[/red] Workspace is required. Pass as argument or set AMI_WORKSPACE.")
        raise typer.Exit(1)

    try:
        client = get_ami_client()
    except typer.Exit:
        raise

    if not yes:
        console.print(f"[yellow]Warning:[/yellow] This will permanently delete all data in workspace [bold]{resolved}[/bold].")
        confirm_input = typer.prompt(f'Type "{resolved}" to confirm')
        if confirm_input != resolved:
            console.print("[red]Aborted.[/red]")
            raise typer.Exit(1)

    console.print(f"[dim]Purging workspace: {resolved}...[/dim]")

    try:
        target_list = [t.strip() for t in targets.split(",")] if targets else None
        result = client.purge_workspace(
            workspace=resolved,
            confirm=resolved,
            targets=target_list,
        )
    except Exception as e:
        console.print(f"[red]Error purging workspace:[/red] {e}")
        raise typer.Exit(1)

    if json_output:
        import json
        console.print(json.dumps(result, indent=2, default=str))
        return

    purged = result.get("purged", {})
    total = result.get("total", 0)

    if purged:
        table = Table(title=f"Purged: {resolved}")
        table.add_column("Target", style="cyan")
        table.add_column("Count", justify="right")
        for target_name, count in purged.items():
            table.add_row(target_name, str(count))
        console.print(table)

    console.print(f"\n[green]✓ Workspace \"{resolved}\" purged ({total} records removed)[/green]")


@app.command("list-emus")
def list_emus(
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug or ID (overrides AMI_ORG env var)"
    ),
    team: Optional[str] = typer.Option(
        None,
        "--team", "-t",
        help="Team slug or ID (overrides AMI_TEAM env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID to filter by (overrides AMI_WORKSPACE env var)"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID to filter by (overrides AMI_PROJECT env var)"
    ),
    local_project: Optional[str] = typer.Option(
        None,
        "--local-project", "-l",
        help="Local project directory name in 'projects/' folder"
    ),
    all_local: bool = typer.Option(
        False,
        "--all-local", "-a",
        help="List EMUs from all local projects in 'projects/' folder"
    ),
    limit: int = typer.Option(
        100,
        "--limit",
        help="Maximum number of EMUs to return (max 500)"
    ),
    offset: int = typer.Option(
        0,
        "--offset",
        help="Number of EMUs to skip (for pagination)"
    ),
) -> None:
    """
    List EMUs from the API or local project directories.

    Uses /v1/emus endpoint with hierarchical filtering.
    Supports both slugs and IDs for org, team, workspace, and project filters.
    By default, lists EMUs from the API. Use --local-project or --all-local
    to list from local files instead.

    Examples:
        # List all EMUs for your organization (from API key scope)
        memrail list-emus

        # Filter by organization slug
        memrail list-emus --org acme-corp

        # Filter by workspace slug (requires org and team)
        memrail list-emus --org acme-corp --team default --workspace production

        # Filter by project slug (requires full hierarchy)
        memrail list-emus --org acme-corp --team default --workspace production --project api

        # List from local project directory
        memrail list-emus --local-project project_a
        memrail list-emus --all-local

        # Pagination
        memrail list-emus --limit 50 --offset 100
    """
    load_environment()

    # If local options specified, list from local files
    if local_project or all_local:
        if all_local:
            result = discover_all_emus()
        else:
            projects_dir = find_projects_dir()
            if not projects_dir:
                console.print("[red]Error:[/red] Could not find 'projects' directory")
                raise typer.Exit(1)

            project_path = projects_dir / local_project
            if not project_path.is_dir():
                console.print(f"[red]Error:[/red] Project directory not found: {project_path}")
                raise typer.Exit(1)

            result = discover_project_emus(project_path)

        if result.errors:
            for error in result.errors:
                console.print(f"[red]Error:[/red] {error}")

        if not result.emus:
            console.print("[yellow]No EMUs found[/yellow]")
            raise typer.Exit(0)

        _display_emus_table(result.emus)
        return

    # List from API using /v1/emus endpoint - resolve from CLI args or env vars
    # Hierarchy: org > team > workspace > project
    # Rule: If a level is passed via CLI, use it. If not passed, use env ONLY if a lower level needs it.
    # Clear levels below the lowest explicitly passed level.
    org_resolved = org or os.environ.get("AMI_ORG")
    # Team: use CLI if passed, else use env if workspace or project passed, else clear if org passed
    team_resolved = team or (os.environ.get("AMI_TEAM") if (workspace or project) else (None if org else os.environ.get("AMI_TEAM")))
    # Workspace: use CLI if passed, else use env if project passed, else clear if org passed
    workspace_resolved = workspace or (os.environ.get("AMI_WORKSPACE") if project else (None if org else os.environ.get("AMI_WORKSPACE")))
    # Project: use CLI if passed, else clear if any higher level passed
    project_resolved = project or (None if (org or workspace) else os.environ.get("AMI_PROJECT"))

    try:
        client = get_ami_client(org=org_resolved, team=team_resolved)
    except typer.Exit:
        raise

    # Show context info
    org_display = org_resolved or "from API key"
    team_display = team_resolved or "from API key"
    workspace_display = workspace_resolved or "all"
    project_display = project_resolved or "all"
    console.print(f"[dim]Fetching EMUs (org={org_display}, team={team_display}, workspace={workspace_display}, project={project_display})...[/dim]")

    try:
        result = client.list_emus(
            org=org_resolved,
            team=team_resolved,
            workspace=workspace_resolved,
            project=project_resolved,
            limit=limit,
            offset=offset,
        )
    except Exception as e:
        console.print(f"[red]Error fetching EMUs:[/red] {e}")
        raise typer.Exit(1)

    emus = result.get("emus", [])
    total = result.get("total", len(emus))
    count = result.get("count", len(emus))
    has_more = result.get("has_more", False)

    if not emus:
        console.print("[yellow]No EMUs found[/yellow]")
        raise typer.Exit(0)

    # Display pagination info
    start = offset + 1
    end = offset + count
    console.print(f"\n[bold]Showing {start}-{end} of {total} EMU(s)[/bold]")
    if has_more:
        next_offset = offset + limit
        console.print(f"[dim]More results available. Use --offset {next_offset} to see next page[/dim]")
    console.print()

    _display_remote_emus_table(emus)


@app.command("get-emu")
def get_emu(
    emu_key: str = typer.Argument(
        ...,
        help="EMU key to retrieve"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID (overrides AMI_PROJECT env var)"
    ),
    org_id: Optional[str] = typer.Option(
        None,
        "--org-id",
        help="Organization ID (takes precedence over --org)"
    ),
    project_id: Optional[str] = typer.Option(
        None,
        "--project-id",
        help="Project ID (takes precedence over --project)"
    ),
) -> None:
    """
    Get details of a specific EMU by key.

    Organization, workspace, and project can be specified via CLI flags or env vars.
    CLI flags always take precedence over environment variables.

    Examples:
        ami get-emu welcome_new_user --org acme --workspace prod --project api
        ami get-emu my.emu.key -o acme -w prod -p api
        ami get-emu my.emu.key  # Uses AMI_ORG, AMI_WORKSPACE, AMI_PROJECT from .env
    """
    load_environment()

    # Resolve from CLI args or env vars
    # Hierarchy: org > team > workspace > project
    # Rule: If a level is passed via CLI, use it. If not passed, use env ONLY if a lower level needs it.
    org_resolved = org or os.environ.get("AMI_ORG")
    org_id_resolved = org_id or os.environ.get("AMI_ORG_ID")
    # Team: use env if workspace or project passed, else clear if org passed
    team_resolved = os.environ.get("AMI_TEAM") if (workspace or project or project_id) else (None if org else os.environ.get("AMI_TEAM"))
    # Workspace: use CLI if passed, else use env if project passed, else clear if org passed
    workspace_resolved = workspace or (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID") if (project or project_id) else (None if org else (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID"))))
    # Project: use CLI if passed, else clear if any higher level passed
    project_resolved = project or (None if (org or workspace) else (project_id or os.environ.get("AMI_PROJECT")))
    project_id_resolved = project_id or (None if (org or workspace) else os.environ.get("AMI_PROJECT_ID"))

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Provide via --workspace or set AMI_WORKSPACE in .env")
        raise typer.Exit(1)

    if not project_resolved and not project_id_resolved:
        console.print("[red]Error:[/red] Project is required. Provide via --project or set AMI_PROJECT in .env")
        raise typer.Exit(1)

    try:
        client = get_ami_client(org=org_resolved, org_id=org_id_resolved, project_id=project_id_resolved)
    except typer.Exit:
        raise

    # Show context info
    org_display = org_id_resolved or org_resolved or "?"
    console.print(f"[dim]Fetching EMU '{emu_key}' from org={org_display}, workspace={workspace_resolved}, project={project_resolved or project_id_resolved}...[/dim]")

    try:
        emu = client.get_emu(
            emu_key=emu_key,
            workspace=workspace_resolved,
            project=project_resolved or project_id_resolved,
        )
    except Exception as e:
        console.print(f"[red]Error fetching EMU:[/red] {e}")
        raise typer.Exit(1)

    # Format created_at
    created_at = emu.get("created_at", "")
    if created_at:
        try:
            from datetime import datetime
            if isinstance(created_at, str):
                dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
                created_at = dt.strftime("%Y-%m-%d %H:%M:%S")
        except (ValueError, TypeError):
            pass

    # Format trigger (may be long)
    trigger = emu.get("trigger", "?")
    if len(trigger) > 60:
        trigger = trigger[:57] + "..."

    # Display EMU details
    panel_content = (
        f"[bold]EMU ID:[/bold] {emu.get('emu_id', '?')}\n"
        f"[bold]EMU Key:[/bold] {emu.get('emu_key', '?')}\n"
        f"[bold]Version:[/bold] {emu.get('version', '?')}\n"
        f"[bold]State:[/bold] {emu.get('state', '?')}\n"
        f"[bold]Trigger:[/bold] {trigger}\n"
        f"[bold]Expected Utility:[/bold] {emu.get('expected_utility', '?')}\n"
        f"[bold]Confidence:[/bold] {emu.get('confidence', '?')}\n"
        f"[bold]Project ID:[/bold] {emu.get('project_id', '?')}\n"
        f"[bold]Workspace ID:[/bold] {emu.get('workspace_id', '?')}\n"
        f"[bold]Created At:[/bold] {created_at}"
    )

    # Add intent if present
    if emu.get("intent"):
        panel_content += f"\n[bold]Intent:[/bold] {emu.get('intent')}"

    # Add action type if present
    if emu.get("action"):
        action_type = emu["action"].get("type", "?")
        panel_content += f"\n[bold]Action Type:[/bold] {action_type}"

    console.print(Panel(panel_content, title=f"EMU: {emu.get('emu_key', emu_key)}", border_style="cyan"))


@app.command("update-emu")
def update_emu(
    file: Path = typer.Argument(
        ...,
        help="Path to JSON file containing EMU definition(s)"
    ),
    emu_key: Optional[str] = typer.Option(
        None,
        "--emu-key", "-k",
        help="Specific EMU key to update (if file contains multiple EMUs)"
    ),
    # Context options
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID (overrides AMI_PROJECT env var)"
    ),
) -> None:
    """
    Update EMU(s) from a JSON file containing EMU definition(s).

    The file may contain either:
    - A single EMU definition object
    - An object with an "emus" array containing multiple EMU definitions

    If --emu-key is provided, only the matching EMU from the file will be updated.
    Otherwise, all EMUs in the file will be updated.

    Each EMU must have required fields: emu_key, trigger, expected_utility, confidence.

    Examples:
        # Update all EMUs from a file
        ami update-emu ./emus.json

        # Update only a specific EMU from the file
        ami update-emu ./emus.json --emu-key welcome.new_user

        # Update with workspace and project context
        ami update-emu ./my-emu.json -w production -p api
    """
    import json

    load_environment()

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

    # Load EMU definition(s) from file
    try:
        with open(file, "r", encoding="utf-8") as f:
            data = json.load(f)
    except json.JSONDecodeError as e:
        console.print(f"[red]Error:[/red] Invalid JSON in {file}: {e}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] Failed to read file {file}: {e}")
        raise typer.Exit(1)

    # Parse EMU definitions from file
    if not isinstance(data, dict):
        console.print("[red]Error:[/red] File must contain an EMU definition object or {\"emus\": [...]}")
        raise typer.Exit(1)

    # Determine if single EMU or multiple EMUs
    if "emus" in data and isinstance(data["emus"], list):
        # Multiple EMUs format: {"emus": [{...}, {...}]}
        emu_defs = data["emus"]
    elif "emu_key" in data:
        # Single EMU format: {...}
        emu_defs = [data]
    else:
        console.print("[red]Error:[/red] File must contain 'emu_key' (single EMU) or 'emus' array (multiple EMUs)")
        raise typer.Exit(1)

    # Filter by emu_key if specified
    if emu_key:
        emu_defs = [e for e in emu_defs if e.get("emu_key") == emu_key]
        if not emu_defs:
            console.print(f"[red]Error:[/red] EMU with key '{emu_key}' not found in file")
            raise typer.Exit(1)

    if not emu_defs:
        console.print("[yellow]No EMUs found in file[/yellow]")
        raise typer.Exit(0)

    # Validate all EMU definitions
    for emu_def in emu_defs:
        if not isinstance(emu_def, dict):
            console.print("[red]Error:[/red] Each EMU must be an object")
            raise typer.Exit(1)

        missing_fields = []
        if not emu_def.get("emu_key"):
            missing_fields.append("emu_key")
        if not emu_def.get("trigger"):
            missing_fields.append("trigger")
        if emu_def.get("expected_utility") is None:
            missing_fields.append("expected_utility")
        if emu_def.get("confidence") is None:
            missing_fields.append("confidence")

        if missing_fields:
            key = emu_def.get("emu_key", "unknown")
            console.print(f"[red]Error:[/red] EMU '{key}' missing required fields: {', '.join(missing_fields)}")
            raise typer.Exit(1)

    # Resolve context from CLI args or env vars
    org_resolved = org or os.environ.get("AMI_ORG")
    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID")
    project_resolved = project or os.environ.get("AMI_PROJECT") or os.environ.get("AMI_PROJECT_ID")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Use --workspace or set AMI_WORKSPACE env var.")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project is required. Use --project or set AMI_PROJECT env var.")
        raise typer.Exit(1)

    # Update EMUs
    try:
        client = get_ami_client(org=org_resolved)
    except typer.Exit:
        raise

    success_count = 0
    error_count = 0

    console.print(f"\n[bold]Updating {len(emu_defs)} EMU(s)...[/bold]\n")

    for emu_def in emu_defs:
        current_key = emu_def["emu_key"]
        try:
            response = client.update_emu(
                emu_key=current_key,
                trigger=emu_def["trigger"],
                expected_utility=emu_def["expected_utility"],
                confidence=emu_def["confidence"],
                policy=emu_def.get("policy"),
                action=emu_def.get("action"),
                intent=emu_def.get("intent"),
                metadata=emu_def.get("metadata"),
                workspace=workspace_resolved,
                project=project_resolved,
            )

            status = response.get("status", "updated")
            if status == "skipped":
                console.print(f"  [yellow]Skipped[/yellow] {current_key}: No changes")
            else:
                updated_emu = response.get("emu", {})
                version = updated_emu.get("version", "?")
                console.print(f"  [green]OK[/green] {current_key} (v{version})")
            success_count += 1

        except Exception as e:
            error_msg = str(e)
            if "not found" in error_msg.lower() or "404" in error_msg:
                console.print(f"  [red]FAIL[/red] {current_key}: Not found")
            else:
                console.print(f"  [red]FAIL[/red] {current_key}: {error_msg}")
            error_count += 1

    console.print(f"\n[bold]Results:[/bold] {success_count} updated, {error_count} failed")

    if error_count > 0:
        raise typer.Exit(1)


@app.command("get-trace")
def get_trace(
    trace_id: str = typer.Argument(
        ...,
        help="Trace ID to retrieve (e.g., trc_01H2X3Y4Z5A6B7C8)"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID (overrides AMI_PROJECT env var)"
    ),
) -> None:
    """
    Get details of a decision trace by trace ID.

    A trace contains the decision-making details from an invoke call,
    including candidate EMUs, the selected EMU(s), and input context.

    Organization, workspace, and project can be specified via CLI flags or env vars.
    CLI flags always take precedence over environment variables.

    Examples:
        ami get-trace trc_01H2X3Y4Z5A6B7C8 --workspace prod --project api
        ami get-trace trc_01H2X3Y4Z5A6B7C8 -w prod -p api
        ami get-trace trc_01H2X3Y4Z5A6B7C8  # Uses AMI_WORKSPACE, AMI_PROJECT from .env
    """
    load_environment()

    # Resolve from CLI args or env vars
    # Hierarchy: org > team > workspace > project
    # Rule: If a level is passed via CLI, use it. If not passed, use env ONLY if a lower level needs it.
    org_resolved = org or os.environ.get("AMI_ORG")
    # Team: use env if workspace or project passed, else clear if org passed
    team_resolved = os.environ.get("AMI_TEAM") if (workspace or project) else (None if org else os.environ.get("AMI_TEAM"))
    # Workspace: use CLI if passed, else use env if project passed, else clear if org passed
    workspace_resolved = workspace or (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID") if project else (None if org else (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID"))))
    # Project: use CLI if passed, else clear if any higher level passed
    project_resolved = project or (None if (org or workspace) else os.environ.get("AMI_PROJECT"))

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Provide via --workspace or set AMI_WORKSPACE in .env")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project is required. Provide via --project or set AMI_PROJECT in .env")
        raise typer.Exit(1)

    try:
        client = get_ami_client(org=org_resolved, team=team_resolved)
    except typer.Exit:
        raise

    # Show context info
    org_display = org_resolved or "?"
    console.print(f"[dim]Fetching trace '{trace_id}' from org={org_display}, workspace={workspace_resolved}, project={project_resolved}...[/dim]")

    try:
        trace = client.get_trace(
            trace_id=trace_id,
            workspace=workspace_resolved,
            project=project_resolved,
        )
    except Exception as e:
        console.print(f"[red]Error fetching trace:[/red] {e}")
        raise typer.Exit(1)

    # Format created_at if present
    created_at = trace.get("created_at", "")
    if created_at:
        try:
            from datetime import datetime
            if isinstance(created_at, str):
                dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
                created_at = dt.strftime("%Y-%m-%d %H:%M:%S")
        except (ValueError, TypeError):
            pass

    # Build panel content
    panel_content = (
        f"[bold]Trace ID:[/bold] {trace.get('trace_id', trace_id)}\n"
        f"[bold]Project ID:[/bold] {trace.get('project_id', '?')}\n"
        f"[bold]Workspace ID:[/bold] {trace.get('workspace_id', '?')}\n"
    )

    if created_at:
        panel_content += f"[bold]Created At:[/bold] {created_at}\n"

    # Show candidates count
    candidates = trace.get("candidates", [])
    panel_content += f"[bold]Candidates:[/bold] {len(candidates)}\n"

    # Show selected EMUs
    selected = trace.get("selected", [])
    if selected:
        selected_keys = [s.get("emu_key", "?") for s in selected] if isinstance(selected, list) else [selected.get("emu_key", "?")]
        panel_content += f"[bold]Selected:[/bold] {', '.join(selected_keys)}\n"
    else:
        panel_content += "[bold]Selected:[/bold] None\n"

    # Show decision reason if present
    if trace.get("decision_reason"):
        panel_content += f"[bold]Decision Reason:[/bold] {trace.get('decision_reason')}\n"

    console.print(Panel(panel_content, title=f"Trace: {trace_id}", border_style="cyan"))

    # If there are candidates, show them in a table
    if candidates:
        console.print("\n[bold]Candidate EMUs:[/bold]")
        table = Table(show_header=True, header_style="bold magenta")
        table.add_column("EMU Key", style="cyan")
        table.add_column("State")
        table.add_column("Expected Utility")
        table.add_column("Confidence")
        table.add_column("Selected", style="green")

        selected_ids = set()
        if selected:
            for s in (selected if isinstance(selected, list) else [selected]):
                if isinstance(s, dict):
                    selected_ids.add(s.get("emu_id") or s.get("emu_key"))

        for candidate in candidates:
            emu_key = candidate.get("emu_key", "?")
            emu_id = candidate.get("emu_id", "")
            is_selected = emu_id in selected_ids or emu_key in selected_ids
            table.add_row(
                emu_key,
                candidate.get("state", "?"),
                str(candidate.get("expected_utility", "?")),
                str(candidate.get("confidence", "?")),
                "✓" if is_selected else "",
            )

        console.print(table)

    # Show inputs if present
    inputs = trace.get("inputs", {})
    if inputs:
        console.print("\n[bold]Inputs:[/bold]")
        # Show a summary of input keys
        if isinstance(inputs, dict):
            for key, value in inputs.items():
                # Truncate long values
                value_str = str(value)
                if len(value_str) > 60:
                    value_str = value_str[:57] + "..."
                console.print(f"  [dim]{key}:[/dim] {value_str}")


# Valid lifecycle states
LIFECYCLE_STATES = ["draft", "shadow", "canary", "active", "inactive", "archived"]


@app.command("change-state")
def lifecycle(
    emu_key: str = typer.Argument(
        ...,
        help="EMU key to update"
    ),
    state: str = typer.Argument(
        ...,
        help="Target lifecycle state (draft, shadow, canary, active, inactive, archived)"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID (overrides AMI_PROJECT env var)"
    ),
    force: bool = typer.Option(
        False,
        "--force", "-f",
        help="Skip confirmation prompt for destructive operations"
    ),
) -> None:
    """
    Update the lifecycle state of an EMU.

    Valid states: draft, shadow, canary, active, inactive, archived

    State descriptions:
      - draft: Initial state, not evaluated in production
      - shadow: Evaluated but actions only logged (not executed)
      - canary: Active for a subset of traffic
      - active: Fully active, executes actions when triggered
      - inactive: Paused, can be reactivated
      - archived: Permanently retired, cannot be reactivated

    Examples:
        memrail change-state my.emu.key active                    # Activate an EMU
        memrail change-state my.emu.key shadow -w prod -p api     # Set to shadow mode
        memrail change-state my.emu.key inactive                  # Temporarily disable
        memrail change-state my.emu.key archived --force          # Archive permanently
    """
    # Validate state
    state_lower = state.lower()
    if state_lower not in LIFECYCLE_STATES:
        console.print(f"[red]Error:[/red] Invalid state '{state}'. Valid states: {', '.join(LIFECYCLE_STATES)}")
        raise typer.Exit(1)

    load_environment()

    # Resolve from CLI args or env vars
    # Hierarchy: org > team > workspace > project
    # Rule: If a level is passed via CLI, use it. If not passed, use env ONLY if a lower level needs it.
    org_resolved = org or os.environ.get("AMI_ORG")
    # Team: use env if workspace or project passed, else clear if org passed
    team_resolved = os.environ.get("AMI_TEAM") if (workspace or project) else (None if org else os.environ.get("AMI_TEAM"))
    # Workspace: use CLI if passed, else use env if project passed, else clear if org passed
    workspace_resolved = workspace or (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID") if project else (None if org else (os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID"))))
    # Project: use CLI if passed, else clear if any higher level passed
    project_resolved = project or (None if (org or workspace) else os.environ.get("AMI_PROJECT"))

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Use --workspace or set AMI_WORKSPACE env var.")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project is required. Use --project or set AMI_PROJECT env var.")
        raise typer.Exit(1)

    # Warn for destructive operations
    if state_lower == "archived" and not force:
        console.print(f"\n[yellow]Warning:[/yellow] Archiving EMU '{emu_key}' is permanent and cannot be undone.")
        confirm = typer.confirm("Do you want to continue?")
        if not confirm:
            console.print("[dim]Operation cancelled[/dim]")
            raise typer.Exit(0)

    # Get API client
    try:
        client = get_ami_client(org=org_resolved, team=team_resolved)
    except typer.Exit:
        raise

    # Show context info
    org_display = org_resolved or "?"
    console.print(f"[dim]Updating lifecycle state for '{emu_key}' in org={org_display}, workspace={workspace_resolved}, project={project_resolved}...[/dim]")

    try:
        result = client.update_emu_lifecycle(
            emu_key=emu_key,
            state=state_lower,
            workspace=workspace_resolved,
            project=project_resolved,
        )
        # Display result
        old_state = result.get("previous_state", "?")
        new_state = result.get("state", result.get("lifecycle_state", state_lower))
        console.print(f"[green]✓[/green] {emu_key}: {old_state} -> {new_state}")
    except Exception as e:
        console.print(f"[red]✗[/red] Failed to update '{emu_key}': {e}")
        raise typer.Exit(1)

@app.command()
def init(
    project_name: str = typer.Argument(
        ...,
        help="Name of the new project"
    ),
) -> None:
    """
    Initialize a new project with EMU template files.

    Creates the project directory structure with sample EMU definitions.

    Example:
        ami init my_new_project
    """
    projects_dir = find_projects_dir()
    if not projects_dir:
        # Create projects directory in current location
        projects_dir = Path.cwd() / "projects"
        projects_dir.mkdir(exist_ok=True)
        console.print(f"[green]Created:[/green] {projects_dir}")

    project_path = projects_dir / project_name
    if project_path.exists():
        console.print(f"[red]Error:[/red] Project already exists: {project_path}")
        raise typer.Exit(1)

    # Create project structure
    emus_dir = project_path / "emus"
    emus_dir.mkdir(parents=True)

    # Create __init__.py
    init_file = emus_dir / "__init__.py"
    init_file.write_text(f'"""EMU definitions for {project_name}."""\n')

    # Create sample EMU file
    sample_file = emus_dir / "sample_emus.py"
    sample_file.write_text(f'''"""
Sample EMU definitions for {project_name}.

EMUs (Executable Memory Units) are defined as Python dictionaries.
Each EMU requires:
- emu_key: Unique identifier (3-128 chars, lowercase, alphanumeric + :_.-)
- trigger: DSL expression that evaluates to true/false
- action: What to execute when triggered

Optional fields:
- policy: Execution policy (mode, priority, cooldown, exclusion_groups)
- expected_utility: Expected value (0.0-1.0)
- confidence: Confidence level (0.0-1.0)
- intent: Human-readable description
- state: Lifecycle state (draft, active, canary, shadow, archived)
"""

# Single EMU definition
welcome_new_user = {{
    "emu_key": "{project_name}.welcome_new_user",
    "trigger": 'state.user.is_new == true AND NOT event.agent.sent.welcome IN "P7D"',
    "action": {{
        "type": "tool_call",
        "intent": "Send welcome message to new user",
        "tool": {{
            "tool_id": "messaging",
            "version": "1.0.0",
            "args": {{"template": "welcome"}}
        }}
    }},
    "policy": {{
        "mode": "auto",
        "priority": 5,
        "cooldown": {{"seconds": 604800, "gate": "activation"}}
    }},
    "expected_utility": 0.8,
    "confidence": 0.9,
    "intent": "Welcome new users with a personalized message",
    "state": "draft"
}}

# List of EMUs (alternative pattern)
EMUS = [
    {{
        "emu_key": "{project_name}.high_priority_alert",
        "trigger": 'state.alert.level == "critical" AND tag.status == "unresolved"',
        "action": {{
            "type": "route",
            "route": "urgent-queue"
        }},
        "policy": {{
            "mode": "auto",
            "priority": 9,
            "exclusion_groups": ["alert_handlers"]
        }},
        "expected_utility": 0.95,
        "confidence": 0.85
    }},
]
''')

    # Create project README
    readme_file = project_path / "README.md"
    readme_file.write_text(f'''# {project_name}

EMU project for SOMA AMI integration.

## Structure

```
{project_name}/
└── emus/
    ├── __init__.py
    └── sample_emus.py    # Your EMU definitions
```

## Usage

```bash
# Validate EMUs locally
memrail validate --project {project_name}

# Register EMUs with API
memrail register-emu --project {project_name}

# Dry-run (validate without registering)
memrail register-emu --project {project_name} --dry-run
```

## EMU Definition Format

See `emus/sample_emus.py` for examples.
''')

    console.print(f"\n[green]Project initialized:[/green] {project_path}")
    console.print(f"\nCreated files:")
    console.print(f"  - {emus_dir / '__init__.py'}")
    console.print(f"  - {sample_file}")
    console.print(f"  - {readme_file}")
    console.print(f"\n[bold]Next steps:[/bold]")
    console.print(f"  1. Edit {sample_file} to define your EMUs")
    console.print(f"  2. Run: memrail validate --project {project_name}")
    console.print(f"  3. Run: memrail register-emu --project {project_name}")


@app.command("archive")
def archive(
    emu_key: Optional[str] = typer.Argument(
        None,
        help="EMU key to archive. If not provided, archives all EMUs in the project."
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug (overrides AMI_ORG env var)"
    ),
    org_id: Optional[str] = typer.Option(
        None,
        "--org-id",
        help="Organization ID (overrides AMI_ORG_ID env var)"
    ),
    team_id: Optional[str] = typer.Option(
        None,
        "--team-id",
        help="Team ID (overrides AMI_TEAM_ID env var)"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID (overrides AMI_WORKSPACE env var)"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID (overrides AMI_PROJECT env var)"
    ),
    force: bool = typer.Option(
        False,
        "--force", "-f",
        help="Skip confirmation prompt"
    ),
) -> None:
    """
    Archive EMUs by setting their lifecycle state to 'archived'.

    If an EMU key is provided, archives that specific EMU (latest version).
    If no EMU key is provided, archives ALL EMUs in the project.

    Examples:
        ami archive my.emu.key                           # Archive specific EMU
        ami archive                                      # Archive all EMUs in project
        ami archive --force                              # Skip confirmation
        ami archive -w prod -p api my.emu.key            # With workspace/project
    """
    load_environment()

    # Resolve hierarchy from CLI args or env vars
    org_resolved = org or os.environ.get("AMI_ORG")
    org_id_resolved = org_id or os.environ.get("AMI_ORG_ID")
    team_id_resolved = team_id or os.environ.get("AMI_TEAM_ID")
    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE") or os.environ.get("AMI_WORKSPACE_ID")
    project_resolved = project or os.environ.get("AMI_PROJECT") or os.environ.get("AMI_PROJECT_ID")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace is required. Use --workspace or set AMI_WORKSPACE env var.")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project is required. Use --project or set AMI_PROJECT env var.")
        raise typer.Exit(1)

    # Get API client
    try:
        client = get_ami_client(org=org_resolved, org_id=org_id_resolved)
    except typer.Exit:
        raise

    # If specific EMU key provided, archive just that one
    if emu_key:
        if not force:
            console.print(f"\n[yellow]Warning:[/yellow] This will archive EMU '{emu_key}' (latest version).")
            confirm = typer.confirm("Do you want to continue?")
            if not confirm:
                console.print("[dim]Operation cancelled[/dim]")
                raise typer.Exit(0)

        console.print(f"\n[dim]Archiving EMU '{emu_key}'...[/dim]")

        try:
            client.update_emu_lifecycle(
                emu_key=emu_key,
                state="archived",
                workspace=workspace_resolved,
                project=project_resolved,
            )
            console.print(f"[green]OK[/green] {emu_key} -> archived")
        except Exception as e:
            console.print(f"[red]FAIL[/red] {emu_key}: {e}")
            raise typer.Exit(1)
        return

    # No EMU key provided - archive all EMUs in the project
    console.print(f"[dim]Fetching EMUs (workspace={workspace_resolved}, project={project_resolved})...[/dim]")

    try:
        emus = client.list_emus(
            org_id=org_id_resolved,
            team_id=team_id_resolved,
            workspace_id=workspace_resolved,
            project_id=project_resolved,
        )
    except Exception as e:
        console.print(f"[red]Error:[/red] Failed to fetch EMUs: {e}")
        raise typer.Exit(1)

    if not emus:
        console.print("[yellow]No EMUs found[/yellow]")
        raise typer.Exit(0)

    # Group by emu_key and keep only latest version
    emu_groups: dict[str, list[dict]] = {}
    for emu in emus:
        key = emu.get("emu_key", emu.get("key", ""))
        if key not in emu_groups:
            emu_groups[key] = []
        emu_groups[key].append(emu)

    latest_emus = []
    for key, versions in emu_groups.items():
        sorted_versions = sorted(versions, key=lambda x: x.get("version", 0), reverse=True)
        latest_emus.append(sorted_versions[0])

    # Display what will be archived
    console.print(f"\n[bold]Found {len(latest_emus)} EMU(s) to archive:[/bold]\n")
    _display_remote_emus_table(latest_emus)

    # Confirm with user
    if not force:
        console.print(f"\n[yellow]Warning:[/yellow] This will archive {len(latest_emus)} EMU(s).")
        confirm = typer.confirm("Do you want to continue?")
        if not confirm:
            console.print("[dim]Operation cancelled[/dim]")
            raise typer.Exit(0)

    # Archive each EMU
    success_count = 0
    error_count = 0

    console.print(f"\n[bold]Archiving EMUs...[/bold]\n")

    for emu in latest_emus:
        key = emu.get("emu_key", emu.get("key"))
        version = emu.get("version", "?")

        if not key:
            continue

        try:
            client.update_emu_lifecycle(
                emu_key=key,
                state="archived",
                workspace=workspace_resolved,
                project=project_resolved,
            )
            console.print(f"  [green]OK[/green] {key} v{version} -> archived")
            success_count += 1
        except Exception as e:
            console.print(f"  [red]FAIL[/red] {key} v{version}: {e}")
            error_count += 1

    console.print(f"\n[bold]Results:[/bold] {success_count} archived, {error_count} failed")

    if error_count > 0:
        raise typer.Exit(1)


def _get_emu_org(emu: dict) -> str | None:
    """Extract org from EMU dict, trying various field names."""
    # Prefer slug over ID for display
    for field in ["org_slug", "org", "org_id", "orgSlug", "orgId"]:
        if emu.get(field):
            return emu[field]
    return None


def _get_emu_project(emu: dict) -> str | None:
    """Extract project from EMU dict, trying various field names."""
    # Prefer slug over ID for display
    for field in ["project_slug", "project", "project_id", "projectSlug", "projectId"]:
        if emu.get(field):
            return emu[field]
    return None


def _get_emu_workspace(emu: dict) -> str | None:
    """Extract workspace from EMU dict, trying various field names."""
    for field in ["workspace_slug", "workspace", "workspace_id", "workspaceSlug", "workspaceId"]:
        if emu.get(field):
            return emu[field]
    return None


def _display_remote_emus_table(emus: list[dict]) -> None:
    """Display remote EMUs in a formatted table."""
    table = Table(title="Registered EMUs")
    table.add_column("EMU Key", style="cyan")
    table.add_column("Org", style="magenta")
    table.add_column("Project", style="green")
    table.add_column("State", style="yellow")
    table.add_column("Version", style="dim")

    for emu in emus:
        table.add_row(
            emu.get("emu_key", emu.get("key", "?")),
            _get_emu_org(emu) or "?",
            _get_emu_project(emu) or "?",
            emu.get("state", "?"),
            str(emu.get("version", "?")),
        )

    console.print(table)


def _display_emus_table(emus: list[DiscoveredEMU]) -> None:
    """Display EMUs in a formatted table."""
    table = Table(title="EMU Definitions")
    table.add_column("EMU Key", style="cyan")
    table.add_column("Project", style="green")
    table.add_column("File", style="dim")
    table.add_column("State", style="yellow")

    for emu in emus:
        state = emu.definition.get("state", "draft")
        table.add_row(
            emu.name,
            emu.project,
            emu.source_file.name,
            state,
        )

    console.print(table)


# =============================================================================
@app.command("tool-list")
def tool_list(
    package: Optional[str] = typer.Option(
        None,
        "--package", "-p",
        help="Python package to scan for tool handlers"
    ),
    path: Optional[Path] = typer.Option(
        None,
        "--path",
        help="Directory path to scan for tool handlers"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-j",
        help="Filter to show only tools for this project"
    ),
    format: str = typer.Option(
        "table",
        "--format", "-f",
        help="Output format: table, json, yaml"
    ),
) -> None:
    """
    List discovered tool handlers in the project.

    Scans for @tool decorators and displays all found tools.
    Auto-detects tools_entry from .ami/config.yaml or pyproject.toml [tool.ami].

    Examples:
        memrail tool-list                           # Uses config.tools_entry
        memrail tool-list -p my_app.tools           # Override with specific package
        memrail tool-list --project customer_support # Filter by project
        memrail tool-list --format json
    """
    load_environment()

    from cli.tool_discovery import discover_tools

    # Auto-load config if no package/path specified
    config_package = package
    if not package and not path:
        try:
            config = load_config()
            if config.tools_entry:
                config_package = config.tools_entry
                console.print(f"[dim]Using tools_entry from config: {config_package}[/dim]\n")
        except ToolConfigError:
            pass  # No config found, will use auto-discovery

    console.print("[bold]Discovering tool handlers...[/bold]\n")

    result = discover_tools(package=config_package, path=path)

    if result.errors:
        for error in result.errors:
            console.print(f"[red]Error:[/red] {error}")
        raise typer.Exit(1)

    if not result.tools:
        console.print("[yellow]No tools found[/yellow]")
        raise typer.Exit(0)

    # Filter by project if specified
    tools_to_display = result.tools
    if project:
        tools_to_display = [t for t in result.tools if project in t.projects]
        if not tools_to_display:
            console.print(f"[yellow]No tools found for project '{project}'[/yellow]")
            # Show available projects
            all_projects = set()
            for t in result.tools:
                all_projects.update(t.projects)
            if all_projects:
                console.print(f"[dim]Available projects: {', '.join(sorted(all_projects))}[/dim]")
            raise typer.Exit(0)

    if format == "json":
        import json
        output = {
            "tools": [
                {
                    "name": t.name,
                    "module": t.module,
                    "file": str(t.source_file),
                    "projects": t.projects,
                }
                for t in tools_to_display
            ],
            "count": len(tools_to_display),
        }
        console.print(json.dumps(output, indent=2))
    elif format == "yaml":
        try:
            import yaml
            output = {
                "tools": [
                    {
                        "name": t.name,
                        "module": t.module,
                        "file": str(t.source_file),
                        "projects": t.projects,
                    }
                    for t in tools_to_display
                ],
                "count": len(tools_to_display),
            }
            console.print(yaml.dump(output, default_flow_style=False))
        except ImportError:
            console.print("[red]Error:[/red] PyYAML required for YAML output: pip install pyyaml")
            raise typer.Exit(1)
    else:
        # Table format
        title = f"Discovered Tools" + (f" (project: {project})" if project else "")
        table = Table(title=title)
        table.add_column("Tool Name", style="cyan")
        table.add_column("Projects", style="yellow")
        table.add_column("Module", style="green")
        table.add_column("File", style="dim")

        for tool in tools_to_display:
            projects_str = ", ".join(tool.projects) if tool.projects else "(none)"
            table.add_row(
                tool.name,
                projects_str,
                tool.module,
                tool.source_file.name,
            )

        console.print(table)
        console.print(f"\n[dim]Found {len(tools_to_display)} tool(s)[/dim]")


@app.command("tool-register")
def tool_register(
    tool: Optional[str] = typer.Option(
        None,
        "--tool", "-t",
        help="Register only this specific tool (by name)"
    ),
    package: Optional[str] = typer.Option(
        None,
        "--package", "-p",
        help="Python package to scan for tool handlers (e.g., 'my_app.tools')"
    ),
    path: Optional[Path] = typer.Option(
        None,
        "--path",
        help="Directory path to scan for tool handlers"
    ),
    file: Optional[Path] = typer.Option(
        None,
        "--file", "-f",
        help="Specific Python file containing tool handlers to register"
    ),
    name: Optional[str] = typer.Option(
        None,
        "--name", "-n",
        help="Schema name (default: project-tools)"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-j",
        help="Project slug or ID"
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run",
        help="Show what would be uploaded without actually uploading"
    ),
    export: Optional[Path] = typer.Option(
        None,
        "--export", "-e",
        help="Also export schema to a local file"
    ),
    verbose: bool = typer.Option(
        False,
        "--verbose", "-v",
        help="Show detailed output"
    ),
) -> None:
    """
    Discover tool handlers and register them with the platform.

    This command:
    1. Auto-discovers @tool handlers in your code
    2. Generates an executor schema from discovered tools
    3. Uploads the schema to the platform

    Use --tool to register a single tool instead of all discovered tools.
    When using --tool, the new tool is merged with existing tools in the project:
    - If the tool already exists, it is updated
    - If it's a new tool, it is added to existing tools

    Auto-detects config from .ami/config.yaml or pyproject.toml [tool.ami].

    Examples:
        # Discover and register all tools
        memrail tool-register

        # Register a single tool (merges with existing tools)
        memrail tool-register --tool send_email --project my-agent

        # Register tools for a specific project
        memrail tool-register -w production -j my-agent

        # Scan specific package
        memrail tool-register -p my_app.tools

        # Register tools from a specific file
        memrail tool-register --file src/tools/handlers.py --project my-agent

        # Dry run - see what would be uploaded
        memrail tool-register --dry-run

        # Also save schema locally
        memrail tool-register --export .ami/tools_schema.json
    """
    import asyncio
    import json

    load_environment()

    from cli.tool_discovery import discover_tools
    from memrail.tools import ToolRegistry

    # Auto-load config for defaults
    config: Optional[AMIToolConfig] = None
    try:
        config = load_config()
    except ToolConfigError:
        pass  # No config found, use CLI args and env vars

    # Resolve config - use config values as fallback
    org_resolved = org or (config.org_id if config else None) or os.environ.get("AMI_ORG")
    workspace_resolved = workspace or (config.workspace if config else None) or os.environ.get("AMI_WORKSPACE")
    # Note: project is only used as a filter - don't resolve from env/config
    # Tools will be registered to their associated projects defined in @tool decorators
    project_filter = project  # Only use explicit --project flag, not env/config
    api_key = os.environ.get("AMI_API_KEY")

    # Resolve package from config if not specified
    config_package = package
    if not package and not path and not file and config and config.tools_entry:
        config_package = config.tools_entry
        console.print(f"[dim]Using tools_entry from config: {config_package}[/dim]")

    # If --file is specified, use its parent directory as path
    scan_path = path
    if file:
        if not file.exists():
            console.print(f"[red]Error:[/red] File not found: {file}")
            raise typer.Exit(1)
        if not file.suffix == ".py":
            console.print(f"[red]Error:[/red] File must be a Python file (.py): {file}")
            raise typer.Exit(1)
        scan_path = file.parent
        console.print(f"[dim]Scanning file: {file}[/dim]")

    if not dry_run:
        if not api_key:
            console.print("[red]Error:[/red] AMI_API_KEY required. Set in .env or environment.")
            raise typer.Exit(1)

        if not workspace_resolved:
            console.print("[red]Error:[/red] Workspace required. Use --workspace, config, or set AMI_WORKSPACE")
            raise typer.Exit(1)

        # Note: project_filter can be None - we'll use projects defined in tool handlers

    # Step 1: Discover handlers
    console.print("[bold]Step 1: Discovering tool handlers...[/bold]")

    result = discover_tools(package=config_package, path=scan_path)

    if result.errors:
        for error in result.errors:
            console.print(f"[red]Error:[/red] {error}")
        raise typer.Exit(1)

    # If --file was specified, filter to only tools from that file
    if file:
        file_resolved = file.resolve()
        result.tools = [t for t in result.tools if t.source_file.resolve() == file_resolved]
        if not result.tools:
            console.print(f"\n[yellow]No tools found in file: {file}[/yellow]")
            raise typer.Exit(0)
        console.print(f"  Found {len(result.tools)} tool(s) in {file.name}")
    else:
        console.print(f"  Scanned {result.modules_scanned} modules")
        console.print(f"  Found {result.modules_with_tools} modules with @tool decorators")
        console.print(f"  Discovered {len(result.tools)} tool handlers")

    if not result.tools:
        console.print("\n[yellow]No tools found to register[/yellow]")
        raise typer.Exit(0)

    if verbose:
        console.print("\n[dim]Discovered tools:[/dim]")
        for discovered_tool in result.tools:
            projects_str = ", ".join(discovered_tool.projects) if discovered_tool.projects else "(no projects)"
            console.print(f"  - {discovered_tool.name} ({discovered_tool.source_file.name}) -> [{projects_str}]")

    # Group tools by project for multi-project registration
    if project_filter:
        if tool:
            # Single tool + project mode - register specific tool to specific project
            # Don't require the tool to have this project in its associations
            tool_exists = any(t.name == tool for t in result.tools)
            if not tool_exists:
                console.print(f"\n[red]Error:[/red] Tool '{tool}' not found in discovered tools")
                raise typer.Exit(1)
            projects_to_register = {project_filter: [tool]}
            console.print(f"  Registering tool '{tool}' to project '{project_filter}'")
        else:
            # Multi-tool + project filter mode - only register tools that belong to this project
            tools_for_project = [t for t in result.tools if project_filter in t.projects]
            if not tools_for_project:
                console.print(f"\n[yellow]No tools found for project '{project_filter}'[/yellow]")
                # Show what projects are available
                all_projects = set()
                for t in result.tools:
                    all_projects.update(t.projects)
                if all_projects:
                    console.print(f"[dim]Available projects: {', '.join(sorted(all_projects))}[/dim]")
                raise typer.Exit(0)
            projects_to_register = {project_filter: [t.name for t in tools_for_project]}
            console.print(f"  {len(tools_for_project)} tool(s) filtered to project '{project_filter}'")
    elif tool:
        # Single tool mode without project filter - use tool's associated projects
        tool_info = next((t for t in result.tools if t.name == tool), None)
        if not tool_info:
            console.print(f"\n[red]Error:[/red] Tool '{tool}' not found in discovered tools")
            raise typer.Exit(1)
        if not tool_info.projects:
            console.print(f"\n[red]Error:[/red] Tool '{tool}' has no projects defined")
            console.print("[dim]Use --project to specify which project to register this tool to[/dim]")
            raise typer.Exit(1)
        projects_to_register = {proj: [tool] for proj in tool_info.projects}
        console.print(f"  Registering tool '{tool}' to {len(projects_to_register)} project(s)")
        for proj in projects_to_register:
            console.print(f"    - {proj}")
    else:
        # Multi-project mode - group tools by their project associations
        projects_to_register: dict[str, list[str]] = {}
        for discovered_tool in result.tools:
            if not discovered_tool.projects:
                console.print(f"[yellow]Warning:[/yellow] Tool '{discovered_tool.name}' has no project associations - use --tool {discovered_tool.name} --project <project> to register it")
                continue
            for proj in discovered_tool.projects:
                if proj not in projects_to_register:
                    projects_to_register[proj] = []
                projects_to_register[proj].append(discovered_tool.name)

        if not projects_to_register:
            console.print("\n[red]Error:[/red] No tools with projects found to register")
            console.print("[dim]Either specify --project or add projects to your @tool decorators[/dim]")
            raise typer.Exit(1)

        console.print(f"  Tools will be registered to {len(projects_to_register)} project(s)")
        for proj, tool_names in projects_to_register.items():
            console.print(f"    - {proj}: {len(tool_names)} tool(s)")

    # Find the registry that was populated during import
    registry = None
    for module_name, module in sys.modules.items():
        if hasattr(module, "registry") and isinstance(getattr(module, "registry"), ToolRegistry):
            registry = getattr(module, "registry")
            break
        if hasattr(module, "tool_registry") and isinstance(getattr(module, "tool_registry"), ToolRegistry):
            registry = getattr(module, "tool_registry")
            break

    if not registry or len(registry) == 0:
        # Create a minimal registry from discovered tools
        console.print("\n[yellow]Note:[/yellow] No populated registry found, using discovered tool names")
        # Use first project from the list for naming, or 'discovered' as fallback
        first_project = next(iter(projects_to_register.keys()), "discovered")
        registry = ToolRegistry(server_name=name or f"{first_project}-tools")
    else:
        # Registry was populated — reconcile projects_to_register to use
        # registry tool names instead of AST-discovered function names.
        # AST discovery produces Python function names (e.g. _handle_foo)
        # while the registry has decorator names (e.g. foo from name="foo").
        # The upload filter (below) intersects these two sets, so they must
        # use the same naming convention.
        registry_tool_names = set(registry._tools.keys())
        for proj in list(projects_to_register.keys()):
            discovered_names = projects_to_register[proj]
            # If discovered names don't appear in the registry, replace
            # with all registry tools that belong to this project.
            if not any(n in registry_tool_names for n in discovered_names):
                matched = [
                    td.name for td in registry._tools.values()
                    if proj in (td.projects or [])
                ]
                if matched:
                    projects_to_register[proj] = matched

    # Step 2: Generate schema
    console.print("\n[bold]Step 2: Generating executor schema...[/bold]")

    schema = registry.export_schema()
    schema_dict = schema.to_dict()

    # Filter to single tool if specified
    if tool:
        all_tools = schema_dict.get("tools", {})
        if tool not in all_tools:
            console.print(f"[red]Error:[/red] Tool '{tool}' not found in discovered tools")
            console.print(f"[dim]Available tools: {', '.join(all_tools.keys())}[/dim]")
            raise typer.Exit(1)
        schema_dict["tools"] = {tool: all_tools[tool]}
        console.print(f"  Filtering to single tool: {tool}")

    # Update server name - use first project or 'tools' as fallback
    first_project = next(iter(projects_to_register.keys()), "unknown")
    schema_dict["server_name"] = name or f"{first_project}-tools"

    tools_count = len(schema_dict.get("tools", {}))
    console.print(f"  Schema version: {schema_dict.get('version', '1.0.0')}")
    console.print(f"  Tools count: {tools_count}")

    if verbose:
        console.print("\n[dim]Schema tools:[/dim]")
        for tool_name, tool_data in schema_dict.get("tools", {}).items():
            has_handler = tool_data.get("has_handler", False)
            status = "[green]✓[/green]" if has_handler else "[yellow]○[/yellow]"
            console.print(f"  {status} {tool_name}")

    # Step 3: Export locally if requested
    if export:
        console.print(f"\n[bold]Exporting schema to {export}...[/bold]")
        export.parent.mkdir(parents=True, exist_ok=True)
        with open(export, "w") as f:
            json.dump(schema_dict, f, indent=2)
        console.print(f"  [green]OK[/green] Saved to {export}")

    # Step 4: Upload to platform (per project)
    if dry_run:
        console.print("\n[bold]Dry run - would upload:[/bold]")
        console.print(f"  Organization: {org_resolved or 'from API key'}")
        console.print(f"  Workspace: {workspace_resolved}")
        for proj, tool_names in projects_to_register.items():
            console.print(f"  Project '{proj}': {len(tool_names)} tool(s)")
        console.print("\n[dim]Use without --dry-run to actually upload[/dim]")
        return

    console.print(f"\n[bold]Step 3: Uploading to platform...[/bold]")
    console.print(f"  Organization: {org_resolved or 'from API key'}")
    console.print(f"  Workspace: {workspace_resolved}")

    async def upload_schema_for_project(proj: str, proj_tools: list[str]):
        import httpx
        api_url = os.environ.get("AMI_API_URL", "https://api.memrail.com")

        proj_schema_dict = schema_dict.copy()

        # If single tool mode, use the tool directly (already filtered in schema_dict)
        # and merge with existing tools in the project
        if tool:  # 'tool' is the CLI arg for single tool name
            new_tools = schema_dict.get("tools", {}).copy()
            async with httpx.AsyncClient(timeout=30.0) as http_client:
                try:
                    get_response = await http_client.get(
                        f"{api_url}/v1/workspaces/{workspace_resolved}/projects/{proj}/executor-schema",
                        headers={
                            "Authorization": f"AMI-Key {api_key}",
                            "X-AMI-Org": org_resolved or "",
                        },
                    )
                    if get_response.status_code == 200:
                        data = get_response.json()
                        schemas = data.get("schemas", [])
                        if schemas:
                            existing_schema = schemas[0]  # Latest schema
                            existing_tools = {}
                            # Handle both list and dict formats from API
                            schema_data = existing_schema.get("schema", {})
                            tools_raw = schema_data.get("tools", [])
                            if isinstance(tools_raw, list):
                                for t in tools_raw:
                                    t_name = t.get("name")
                                    if t_name:
                                        existing_tools[t_name] = t
                            elif isinstance(tools_raw, dict):
                                existing_tools = tools_raw

                            # Merge: new tool overwrites if exists
                            merged_tools = {**existing_tools, **new_tools}
                            new_tools = merged_tools
                            console.print(f"    [dim]Merging with {len(existing_tools)} existing tool(s)[/dim]")
                except Exception as e:
                    console.print(f"    [yellow]Warning:[/yellow] Could not fetch existing schema: {e}")
                    # Continue with just the new tool

            # Update the tool's projects annotation to include the target project
            for tool_name, tool_data in new_tools.items():
                if "projects" not in tool_data:
                    tool_data["projects"] = []
                if proj not in tool_data["projects"]:
                    tool_data["projects"].append(proj)
                    console.print(f"    [dim]Updated '{tool_name}' projects annotation to include '{proj}'[/dim]")
        else:
            # Multi-tool mode: filter to tools that belong to this project
            new_tools = {
                tool_name: data for tool_name, data in schema_dict.get("tools", {}).items()
                if tool_name in proj_tools
            }

        proj_schema_dict["tools"] = new_tools
        proj_schema_dict["server_name"] = name or f"{proj}-tools"

        async with httpx.AsyncClient(timeout=30.0) as http_client:
            response = await http_client.post(
                f"{api_url}/v1/workspaces/{workspace_resolved}/projects/{proj}/executor-schema",
                json={
                    "name": proj_schema_dict.get("server_name", f"{proj}-tools"),
                    "version": proj_schema_dict.get("version", "1.0.0"),
                    "schema": proj_schema_dict,
                },
                headers={
                    "Authorization": f"AMI-Key {api_key}",
                    "X-AMI-Org": org_resolved or "",
                },
            )
            response.raise_for_status()
            return response.json()

    async def upload_all():
        results = {}
        for proj, tool_names in projects_to_register.items():
            console.print(f"\n  Uploading to project '{proj}'...")
            try:
                result = await upload_schema_for_project(proj, tool_names)
                results[proj] = {"success": True, "result": result}
                console.print(f"    [green]✓[/green] {len(tool_names)} tool(s) uploaded")
            except Exception as e:
                results[proj] = {"success": False, "error": str(e)}
                console.print(f"    [red]✗[/red] Error: {e}")
        return results

    try:
        upload_results = asyncio.run(upload_all())
        success_count = sum(1 for r in upload_results.values() if r["success"])
        error_count = len(upload_results) - success_count

        console.print(f"\n[bold]Results:[/bold]")
        if success_count > 0:
            console.print(f"  [green]Success:[/green] {success_count} project(s)")
        if error_count > 0:
            console.print(f"  [red]Failed:[/red] {error_count} project(s)")

        if error_count > 0:
            raise typer.Exit(1)
    except typer.Exit:
        raise
    except Exception as e:
        console.print(f"\n[red]Error uploading:[/red] {e}")
        raise typer.Exit(1)


@app.command("tool-export")
def tool_export(
    package: Optional[str] = typer.Option(
        None,
        "--package", "-p",
        help="Python package to scan for tool handlers"
    ),
    path: Optional[Path] = typer.Option(
        None,
        "--path",
        help="Directory path to scan for tool handlers"
    ),
    output: Optional[Path] = typer.Option(
        None,
        "--output", "-o",
        help="Output file path (default: from config.schema_path or .ami/tools_schema.json)"
    ),
    format: str = typer.Option(
        "json",
        "--format", "-f",
        help="Output format: json, yaml"
    ),
    verbose: bool = typer.Option(
        False,
        "--verbose", "-v",
        help="Show detailed output"
    ),
) -> None:
    """
    Export tool schema to a local file.

    Auto-discovers handlers by scanning for @tool decorators in your code,
    then exports the schema for review before uploading.

    Auto-detects config from .ami/config.yaml or pyproject.toml [tool.ami].

    Examples:
        # Export to config.schema_path (default: .ami/tools_schema.json)
        memrail tool-export

        # Export to custom path
        memrail tool-export -o schema.json

        # Export as YAML
        memrail tool-export --format yaml -o schema.yaml
    """
    import json

    load_environment()

    from cli.tool_discovery import discover_tools
    from memrail.tools import ToolRegistry

    # Auto-load config for defaults
    config: Optional[AMIToolConfig] = None
    try:
        config = load_config()
    except ToolConfigError:
        pass

    # Resolve package from config if not specified
    config_package = package
    if not package and not path and config and config.tools_entry:
        config_package = config.tools_entry
        console.print(f"[dim]Using tools_entry from config: {config_package}[/dim]")

    # Resolve output path from config if not specified
    output_path = output
    if not output_path:
        if config:
            output_path = config.resolve_schema_path()
        else:
            output_path = Path(".ami/tools_schema.json")

    console.print("[bold]Discovering tool handlers...[/bold]")

    result = discover_tools(package=config_package, path=path)

    if result.errors:
        for error in result.errors:
            console.print(f"[red]Error:[/red] {error}")
        raise typer.Exit(1)

    console.print(f"  Scanned {result.modules_scanned} modules")
    console.print(f"  Found {result.modules_with_tools} modules with @tool decorators")
    console.print(f"  Discovered {len(result.tools)} tool handlers")

    if not result.tools:
        console.print("\n[yellow]No tools found to export[/yellow]")
        raise typer.Exit(0)

    # Find the registry that was populated during import
    registry = None
    for module_name, module in sys.modules.items():
        if hasattr(module, "registry") and isinstance(getattr(module, "registry"), ToolRegistry):
            registry = getattr(module, "registry")
            break
        if hasattr(module, "tool_registry") and isinstance(getattr(module, "tool_registry"), ToolRegistry):
            registry = getattr(module, "tool_registry")
            break

    if not registry:
        registry = ToolRegistry()

    schema = registry.export_schema()
    schema_dict = schema.to_dict()

    tools_count = len(schema_dict.get("tools", {}))

    if verbose:
        console.print("\n[dim]Tools in schema:[/dim]")
        for tool_name, tool_data in schema_dict.get("tools", {}).items():
            has_handler = tool_data.get("has_handler", False)
            status = "[green]✓[/green]" if has_handler else "[yellow]○[/yellow]"
            console.print(f"  {status} {tool_name}")

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

    # Write schema
    console.print(f"\n[bold]Exporting schema to {output_path}...[/bold]")

    if format == "yaml":
        try:
            import yaml
            with open(output_path, "w") as f:
                yaml.dump(schema_dict, f, default_flow_style=False, sort_keys=False)
        except ImportError:
            console.print("[red]Error:[/red] PyYAML required for YAML output: pip install pyyaml")
            raise typer.Exit(1)
    else:
        with open(output_path, "w") as f:
            json.dump(schema_dict, f, indent=2)

    console.print(f"[green]OK[/green] Exported {tools_count} tools to {output_path}")


# =============================================================================
# Tool Registry Server CLI Commands
# =============================================================================


def _display_server_tools_table(tools: list) -> None:
    """Display server tools in a formatted table."""
    table = Table(title="Registered Tools")
    table.add_column("Name", style="cyan")
    table.add_column("Description", style="green", max_width=50)
    table.add_column("Handler", style="yellow")
    table.add_column("Capabilities", style="magenta")

    for tool in tools:
        has_handler = tool.get("has_handler", False)
        handler_status = "[green]✓[/green]" if has_handler else "[red]✗[/red]"
        capabilities = ", ".join(tool.get("capabilities", [])) or "-"
        description = tool.get("description", "-")
        if len(description) > 50:
            description = description[:47] + "..."
        table.add_row(
            tool.get("name", "?"),
            description,
            handler_status,
            capabilities,
        )

    console.print(table)
    console.print(f"\n[dim]Total: {len(tools)} tool(s)[/dim]")


@app.command("tool-get-schema")
def tool_get_schema(
    tool_name: Optional[str] = typer.Argument(
        None,
        help="Tool name to retrieve (omit to show all tools)"
    ),
    schema: bool = typer.Option(
        False,
        "--schema", "-s",
        help="Output raw JSON schema instead of formatted view"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    format: str = typer.Option(
        "table",
        "--format", "-f",
        help="Output format: table, json, yaml"
    ),
) -> None:
    """
    Get tool schema(s) from the server.

    Shows registered tools and their input schemas. If no tool name is
    provided, shows all tools. If a tool name is given, shows details
    for that specific tool.

    Examples:
        memrail tool-get                      # Show all tools
        memrail tool-get escalate_to_human    # Show specific tool
        memrail tool-get --schema             # Output full schema as JSON
        memrail tool-get my_tool --schema     # Output specific tool schema
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        # Get latest schema for project
        result = client.list_tool_schemas(
            workspace=workspace_resolved,
            project=project_resolved,
        )
        schemas = result.get("schemas", [])

        if not schemas:
            console.print("[yellow]No executor schema found for this project[/yellow]")
            raise typer.Exit(1)

        # Use the latest schema (first in list, sorted by updated_at desc)
        latest_schema = schemas[0]
        tools = latest_schema.get("schema", {}).get("tools", [])

        if tool_name:
            # Find specific tool
            tool = next((t for t in tools if t.get("name") == tool_name), None)
            if not tool:
                console.print(f"[red]Error:[/red] Tool '{tool_name}' not found in schema")
                console.print(f"[dim]Available tools: {', '.join(t.get('name', '?') for t in tools)}[/dim]")
                raise typer.Exit(1)
            tools = [tool]

        # --schema flag outputs raw schema
        if schema:
            if tool_name:
                _output_format(tools[0], "json" if format == "table" else format)
            else:
                _output_format(latest_schema, "json" if format == "table" else format)
            return

        if format == "table":
            for tool in tools:
                console.print(f"\n[bold cyan]{tool.get('name', '?')}[/bold cyan]")
                if tool.get("description"):
                    console.print(f"  {tool.get('description')}")

                input_schema = tool.get("inputSchema", {})
                properties = input_schema.get("properties", {})
                required = input_schema.get("required", [])

                if properties:
                    console.print("\n  [bold]Parameters:[/bold]")
                    for prop_name, prop_def in properties.items():
                        req_marker = "[red]*[/red]" if prop_name in required else " "
                        prop_type = prop_def.get("type", "any")
                        prop_desc = prop_def.get("description", "")
                        console.print(f"    {req_marker} [green]{prop_name}[/green] ({prop_type})")
                        if prop_desc:
                            console.print(f"        {prop_desc}")
                        if "enum" in prop_def:
                            console.print(f"        [dim]values: {', '.join(prop_def['enum'])}[/dim]")
                else:
                    console.print("  [dim]No parameters[/dim]")

            if not tool_name:
                console.print(f"\n[dim]Schema: {latest_schema.get('name')} | {len(tools)} tool(s)[/dim]")
        else:
            _output_format({"tools": tools}, format)

    except typer.Exit:
        raise
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("tool-schema-delete")
def tool_schema_delete(
    schema_id: str = typer.Argument(
        ...,
        help="Schema ID to delete"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    force: bool = typer.Option(
        False,
        "--force", "-f",
        help="Skip confirmation prompt"
    ),
) -> None:
    """
    Delete an executor schema.

    Removes the schema and all its tool definitions from the server.

    Examples:
        memrail tool-schema-delete 507f1f77bcf86cd799439011
        memrail tool-schema-delete 507f1f77bcf86cd799439011 --force
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    if not force:
        confirm = typer.confirm(f"Are you sure you want to delete schema '{schema_id}'?")
        if not confirm:
            console.print("[yellow]Cancelled[/yellow]")
            raise typer.Exit(0)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        client.delete_tool_schema(
            schema_id,
            workspace=workspace_resolved,
            project=project_resolved,
        )
        console.print(f"[green]OK[/green] Schema '{schema_id}' deleted")

    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("action-connectivity")
def action_connectivity(
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    schema_id: Optional[str] = typer.Option(
        None,
        "--schema-id", "-s",
        help="Specific schema ID to check against (default: latest)"
    ),
    format: str = typer.Option(
        "table",
        "--format", "-f",
        help="Output format: table, json, yaml"
    ),
) -> None:
    """
    Check action connectivity for EMUs in a project.

    Validates that all EMU actions have corresponding handlers registered
    in the executor schema. Identifies missing tools and orphaned tools.

    Examples:
        ami action-connectivity -w dev -p my-agent
        ami action-connectivity --schema-id 507f1f77bcf86cd799439011
        ami action-connectivity --format json
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.check_action_connectivity(
            workspace=workspace_resolved,
            project=project_resolved,
            schema_id=schema_id,
        )

        if format == "table":
            total_emus = result.get("total_emus", 0)
            missing_tools = result.get("missing_tools", [])
            orphaned_tools = result.get("orphaned_tools", [])
            tool_usage = result.get("tool_usage", {})
            validation_results = result.get("validation_results", [])

            console.print(f"\n[bold]Action Connectivity Report[/bold]")
            console.print(f"Total EMUs checked: {total_emus}")

            # Show reachable EMUs and their tools
            if tool_usage:
                # Build EMU -> tools mapping
                emu_tools: dict[str, list[str]] = {}
                for tool_name, emu_keys in tool_usage.items():
                    for emu_key in emu_keys:
                        if emu_key not in emu_tools:
                            emu_tools[emu_key] = []
                        emu_tools[emu_key].append(tool_name)

                if emu_tools:
                    console.print(f"\n[green]Reachable EMUs ({len(emu_tools)}):[/green]")
                    table = Table(show_header=True, header_style="bold")
                    table.add_column("EMU Key", style="cyan")
                    table.add_column("Tools", style="green")
                    for emu_key, tools in sorted(emu_tools.items()):
                        table.add_row(emu_key, ", ".join(sorted(tools)))
                    console.print(table)

            if missing_tools:
                console.print(f"\n[red]Missing Tools ({len(missing_tools)}):[/red]")
                for tool in missing_tools:
                    console.print(f"  - {tool}")

            if orphaned_tools:
                console.print(f"\n[yellow]Orphaned Tools ({len(orphaned_tools)}):[/yellow]")
                console.print("[dim](registered but not used by any EMU)[/dim]")
                for tool in orphaned_tools:
                    console.print(f"  - {tool}")

            if validation_results:
                # Group by severity
                errors = [v for v in validation_results if v.get("severity") == "error"]
                warnings = [v for v in validation_results if v.get("severity") == "warning"]

                if errors:
                    console.print(f"\n[red]Errors ({len(errors)}):[/red]")
                    for issue in errors[:10]:  # Show first 10
                        console.print(f"  [{issue.get('emu_key')}] {issue.get('message')}")
                    if len(errors) > 10:
                        console.print(f"  [dim]... and {len(errors) - 10} more[/dim]")

                if warnings:
                    console.print(f"\n[yellow]Warnings ({len(warnings)}):[/yellow]")
                    for issue in warnings[:10]:  # Show first 10
                        console.print(f"  [{issue.get('emu_key')}] {issue.get('message')}")
                    if len(warnings) > 10:
                        console.print(f"  [dim]... and {len(warnings) - 10} more[/dim]")

            if not missing_tools and not validation_results:
                console.print("\n[green]All actions are connected![/green]")

        else:
            _output_format(result, format)

    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


# =============================================================================
# Prompt Registry CLI Commands
# =============================================================================


def _format_datetime(dt_str: Optional[str]) -> str:
    """Format ISO datetime string for display."""
    if not dt_str:
        return "-"
    try:
        from datetime import datetime
        if isinstance(dt_str, str):
            dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
            return dt.strftime("%Y-%m-%d %H:%M")
    except (ValueError, TypeError):
        pass
    return str(dt_str)


def _output_format(data: dict, format_type: str) -> None:
    """Output data in the specified format."""
    import json
    if format_type == "json":
        console.print(json.dumps(data, indent=2, default=str))
    elif format_type == "yaml":
        try:
            import yaml
            console.print(yaml.dump(data, default_flow_style=False))
        except ImportError:
            console.print("[red]Error:[/red] PyYAML required for YAML output: pip install pyyaml")
            raise typer.Exit(1)


def _display_prompts_table(prompts: list, folders: list = None) -> None:
    """Display prompts in a formatted table."""
    table = Table(title="Prompts")
    table.add_column("Slug", style="cyan")
    table.add_column("Name", style="white")
    table.add_column("Category", style="yellow")
    table.add_column("Model", style="magenta")
    table.add_column("Version", style="dim")
    table.add_column("Folder", style="green")
    table.add_column("Updated", style="dim")

    for prompt in prompts:
        table.add_row(
            prompt.get("slug", "?"),
            prompt.get("name", "?"),
            prompt.get("category", "-"),
            prompt.get("model", "-"),
            f"v{prompt.get('active_version', '?')}",
            prompt.get("folder_path") or "/",
            _format_datetime(prompt.get("updated_at")),
        )

    console.print(table)

    if folders:
        folder_names = [f["path"] for f in folders]
        console.print(f"\n[dim]Folders: {', '.join(folder_names)}[/dim]")

    console.print(f"\n[dim]Total: {len(prompts)} prompt(s)[/dim]")


def _display_prompt_panel(prompt: dict, show_content: bool = True) -> None:
    """Display prompt details in a panel."""
    panel_content = (
        f"[bold]Prompt ID:[/bold] {prompt.get('prompt_id', '?')}\n"
        f"[bold]Slug:[/bold] {prompt.get('slug', '?')}\n"
        f"[bold]Name:[/bold] {prompt.get('name', '?')}\n"
        f"[bold]Description:[/bold] {prompt.get('description') or '-'}\n"
        f"[bold]Category:[/bold] {prompt.get('category') or '-'}\n"
        f"[bold]Model:[/bold] {prompt.get('model') or '-'}\n"
        f"[bold]Max Tokens:[/bold] {prompt.get('max_tokens') or '-'}\n"
        f"[bold]Max Length:[/bold] {prompt.get('max_length') or '-'}\n"
        f"[bold]Active Version:[/bold] v{prompt.get('active_version', '?')}\n"
        f"[bold]Latest Version:[/bold] v{prompt.get('latest_version', '?')}\n"
        f"[bold]Folder:[/bold] {prompt.get('folder_path') or '/'}\n"
        f"[bold]Tags:[/bold] {', '.join(prompt.get('tags', [])) or '-'}\n"
        f"[bold]Created:[/bold] {_format_datetime(prompt.get('created_at'))}\n"
        f"[bold]Updated:[/bold] {_format_datetime(prompt.get('updated_at'))}"
    )

    console.print(Panel(panel_content, title=f"Prompt: {prompt.get('name', '?')}", border_style="green"))

    if show_content:
        content = prompt.get("content", "")
        console.print("\n[bold]Content:[/bold]")
        console.print(Panel(content, border_style="dim"))


def _display_versions_table(versions: list, active_version: int, latest_version: int) -> None:
    """Display prompt versions in a table."""
    console.print(f"\n[dim]Active: v{active_version} | Latest: v{latest_version}[/dim]\n")

    table = Table(title="Versions")
    table.add_column("Version", style="cyan")
    table.add_column("Active", style="green")
    table.add_column("Change Reason", style="white")
    table.add_column("Created At", style="dim")

    for v in versions:
        is_active = v.get("version") == active_version
        table.add_row(
            f"v{v.get('version')}",
            "✓" if is_active else "",
            v.get("change_reason") or "-",
            _format_datetime(v.get("created_at")),
        )

    console.print(table)


def _display_folders_table(folders: list) -> None:
    """Display folders in a formatted table."""
    table = Table(title="Prompt Folders")
    table.add_column("Path", style="cyan")
    table.add_column("Name", style="white")
    table.add_column("Prompts", style="yellow")

    for folder in folders:
        table.add_row(
            folder.get("path", "?"),
            folder.get("name", "?"),
            str(folder.get("prompt_count", 0)),
        )

    console.print(table)
    console.print(f"\n[dim]Total: {len(folders)} folder(s)[/dim]")


def _display_analytics_panel(analytics: dict) -> None:
    """Display prompt analytics in a panel."""
    total = analytics.get("total_fetches", 0)
    panel_content = f"[bold]Total Fetches:[/bold] {total:,}"

    console.print(Panel(panel_content, title="Prompt Analytics", border_style="blue"))

    # Version breakdown table
    version_breakdown = analytics.get("version_breakdown", [])
    if version_breakdown:
        table = Table(title="Version Breakdown")
        table.add_column("Version", style="cyan")
        table.add_column("Fetches", style="white")
        table.add_column("Percentage", style="yellow")

        for v in version_breakdown:
            pct = (v.get("fetches", 0) / total * 100) if total > 0 else 0
            table.add_row(
                f"v{v.get('version')}",
                f"{v.get('fetches', 0):,}",
                f"{pct:.1f}%",
            )

        console.print(table)

    # SDK breakdown table
    sdk_breakdown = analytics.get("sdk_breakdown", [])
    if sdk_breakdown:
        table = Table(title="SDK Breakdown")
        table.add_column("SDK", style="cyan")
        table.add_column("Fetches", style="white")
        table.add_column("Percentage", style="yellow")

        for s in sdk_breakdown:
            pct = (s.get("fetches", 0) / total * 100) if total > 0 else 0
            table.add_row(
                s.get("sdk", "?"),
                f"{s.get('fetches', 0):,}",
                f"{pct:.1f}%",
            )

        console.print(table)


@app.command("prompt-list")
def prompt_list(
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    folder: Optional[str] = typer.Option(
        None,
        "--folder",
        help="Filter by folder path (e.g., 'onboarding' or 'support/tier-1')"
    ),
    limit: int = typer.Option(
        50,
        "--limit", "-l",
        help="Maximum prompts to return (max 100)"
    ),
    offset: int = typer.Option(
        0,
        "--offset",
        help="Pagination offset"
    ),
    category: Optional[str] = typer.Option(
        None,
        "--category", "-c",
        help="Filter by category"
    ),
    model: Optional[str] = typer.Option(
        None,
        "--model", "-m",
        help="Filter by model"
    ),
    tags: Optional[str] = typer.Option(
        None,
        "--tags", "-t",
        help="Filter by tags (comma-separated)"
    ),
    format: str = typer.Option(
        "table",
        "--format", "-f",
        help="Output format: table, json, yaml"
    ),
) -> None:
    """
    List prompts in a project.

    Examples:
        ami prompt-list -w dev -p my-project
        ami prompt-list --folder onboarding -w dev -p my-project
        ami prompt-list --category system --format json
        ami prompt-list --tags email,onboarding
    """
    load_environment()

    # Resolve workspace and project
    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        tags_list = tags.split(",") if tags else None
        result = client.list_prompts(
            workspace=workspace_resolved,
            project=project_resolved,
            limit=limit,
            offset=offset,
            category=category,
            model=model,
            tags=tags_list,
        )

        prompts = result.get("items", [])
        folders = result.get("folders", [])

        # Filter by folder if specified
        if folder:
            prompts = [p for p in prompts if p.get("folder_path") == folder]
            # Filter subfolders to show only those under the specified folder
            folders = [f for f in folders if f.get("path", "").startswith(folder + "/") or f.get("path") == folder]

        if format == "table":
            if not prompts and not folders:
                if folder:
                    console.print(f"[yellow]No prompts found in folder '{folder}'[/yellow]")
                else:
                    console.print("[yellow]No prompts found[/yellow]")
            else:
                _display_prompts_table(prompts, folders)
        else:
            if folder:
                result["items"] = prompts
                result["folders"] = folders
            _output_format(result, format)

    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-get")
def prompt_get(
    slug: str = typer.Argument(
        ...,
        help="Prompt slug to retrieve"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    version: Optional[int] = typer.Option(
        None,
        "--version", "-v",
        help="Specific version number"
    ),
    format: str = typer.Option(
        "panel",
        "--format", "-f",
        help="Output format: panel, json, yaml"
    ),
    show_content: bool = typer.Option(
        True,
        "--content/--no-content",
        help="Show prompt content"
    ),
) -> None:
    """
    Get a prompt by slug.

    Examples:
        ami prompt-get welcome-email -w dev -p my-project
        ami prompt-get welcome-email --version 2
        ami prompt-get welcome-email --format json --no-content
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.get_prompt(
            slug,
            workspace=workspace_resolved,
            project=project_resolved,
            version=version,
        )

        if format == "panel":
            _display_prompt_panel(result, show_content)
        else:
            if not show_content:
                result = {k: v for k, v in result.items() if k != "content"}
            _output_format(result, format)

    except AMINotFound as e:
        if version:
            console.print(f"[red]Error:[/red] Prompt '{slug}' or version {version} not found")
            console.print(f"[dim]Use 'ami prompt-versions {slug}' to see available versions[/dim]")
        else:
            console.print(f"[red]Error:[/red] Prompt '{slug}' not found")
            console.print(f"[dim]Use 'ami prompt-list' to see available prompts[/dim]")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-create")
def prompt_create(
    slug: str = typer.Argument(
        ...,
        help="URL-safe identifier (2-64 chars)"
    ),
    name: str = typer.Argument(
        ...,
        help="Human-readable display name"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    content: Optional[str] = typer.Option(
        None,
        "--content", "-c",
        help="Prompt template content"
    ),
    content_file: Optional[Path] = typer.Option(
        None,
        "--content-file", "-F",
        help="Read content from file"
    ),
    folder_path: Optional[str] = typer.Option(
        None,
        "--folder",
        help="Folder path"
    ),
    description: Optional[str] = typer.Option(
        None,
        "--description", "-d",
        help="Description"
    ),
    category: Optional[str] = typer.Option(
        None,
        "--category",
        help="Category (system/user/assistant)"
    ),
    model: Optional[str] = typer.Option(
        None,
        "--model", "-m",
        help="Target LLM model"
    ),
    max_tokens: Optional[int] = typer.Option(
        None,
        "--max-tokens",
        help="Token budget"
    ),
    max_length: Optional[int] = typer.Option(
        None,
        "--max-length",
        help="Character limit"
    ),
    temperature: Optional[float] = typer.Option(
        None,
        "--temperature",
        help="Sampling temperature (0.0-2.0)"
    ),
    tags: Optional[str] = typer.Option(
        None,
        "--tags", "-t",
        help="Tags (comma-separated)"
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run", "-n",
        help="Validate without creating"
    ),
) -> None:
    """
    Create a new prompt.

    Examples:
        ami prompt-create welcome-email "Welcome Email" --content "Hello {{name}}" -w dev -p my-project
        ami prompt-create greeting "Greeting" --content-file ./prompt.txt
        ami prompt-create onboarding-intro "Intro" -c "Welcome!" --folder onboarding --tags intro,email
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    # Resolve content
    prompt_content = content
    if content_file:
        if not content_file.exists():
            console.print(f"[red]Error:[/red] Content file not found: {content_file}")
            raise typer.Exit(1)
        prompt_content = content_file.read_text()

    if not prompt_content:
        console.print("[red]Error:[/red] Content required. Use --content or --content-file")
        raise typer.Exit(1)

    from memrail.prompts.models import PromptCreate

    prompt_data = PromptCreate(
        slug=slug,
        name=name,
        content=prompt_content,
        folder_path=folder_path,
        description=description,
        category=category,
        model=model,
        max_tokens=max_tokens,
        max_length=max_length,
        temperature=temperature,
        tags=tags.split(",") if tags else [],
    )

    if dry_run:
        console.print("\n[bold]Dry run - would create:[/bold]")
        console.print(f"  Slug: {slug}")
        console.print(f"  Name: {name}")
        console.print(f"  Folder: {folder_path or '/'}")
        console.print(f"  Content length: {len(prompt_content)} chars")
        console.print("\n[dim]Use without --dry-run to actually create[/dim]")
        return

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.create_prompt(
            prompt_data,
            workspace=workspace_resolved,
            project=project_resolved,
        )

        console.print(f"[green]OK[/green] Created prompt '{slug}' (v1)")
        _display_prompt_panel(result, show_content=False)

    except AMIConflict as e:
        console.print(f"[red]Error:[/red] Prompt with slug '{slug}' already exists in this project")
        console.print(f"[dim]Use a different slug or update the existing prompt with:[/dim]")
        console.print(f"[dim]  ami prompt-update {slug} --content '...'[/dim]")
        raise typer.Exit(1)
    except AMINotFound as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)
    except AMIBadRequest as e:
        console.print(f"[red]Error:[/red] Invalid request - {e}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-update")
def prompt_update(
    slug: str = typer.Argument(
        ...,
        help="Prompt slug to update"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    name: Optional[str] = typer.Option(
        None,
        "--name",
        help="New display name"
    ),
    content: Optional[str] = typer.Option(
        None,
        "--content", "-c",
        help="New content"
    ),
    content_file: Optional[Path] = typer.Option(
        None,
        "--content-file", "-F",
        help="Read content from file"
    ),
    description: Optional[str] = typer.Option(
        None,
        "--description", "-d",
        help="New description"
    ),
    category: Optional[str] = typer.Option(
        None,
        "--category",
        help="New category"
    ),
    model: Optional[str] = typer.Option(
        None,
        "--model", "-m",
        help="New model"
    ),
    max_tokens: Optional[int] = typer.Option(
        None,
        "--max-tokens",
        help="New token budget"
    ),
    max_length: Optional[int] = typer.Option(
        None,
        "--max-length",
        help="New character limit"
    ),
    tags: Optional[str] = typer.Option(
        None,
        "--tags", "-t",
        help="New tags (comma-separated)"
    ),
    change_reason: Optional[str] = typer.Option(
        None,
        "--reason", "-r",
        help="Reason for change (audit)"
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run", "-n",
        help="Validate without updating"
    ),
) -> None:
    """
    Update a prompt (creates a new version).

    Examples:
        ami prompt-update welcome-email --content "New content" -w dev -p my-project
        ami prompt-update welcome-email --name "New Name" --reason "Renamed"
        ami prompt-update greeting --content-file ./updated.txt
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    # Resolve content from file
    prompt_content = content
    if content_file:
        if not content_file.exists():
            console.print(f"[red]Error:[/red] Content file not found: {content_file}")
            raise typer.Exit(1)
        prompt_content = content_file.read_text()

    # Check if any update field is provided
    if not any([name, prompt_content, description, category, model, max_tokens, max_length, tags]):
        console.print("[red]Error:[/red] At least one field must be specified for update")
        raise typer.Exit(1)

    from memrail.prompts.models import PromptUpdate

    update_data = PromptUpdate(
        name=name,
        content=prompt_content,
        description=description,
        category=category,
        model=model,
        max_tokens=max_tokens,
        max_length=max_length,
        tags=tags.split(",") if tags else None,
        change_reason=change_reason,
    )

    if dry_run:
        console.print("\n[bold]Dry run - would update:[/bold]")
        console.print(f"  Slug: {slug}")
        for field, value in update_data.to_dict().items():
            console.print(f"  {field}: {value}")
        console.print("\n[dim]Use without --dry-run to actually update[/dim]")
        return

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.update_prompt(
            slug,
            update_data,
            workspace=workspace_resolved,
            project=project_resolved,
        )

        new_version = result.get("latest_version", "?")
        console.print(f"[green]OK[/green] Updated prompt '{slug}' (now v{new_version})")

    except AMINotFound as e:
        console.print(f"[red]Error:[/red] Prompt '{slug}' not found")
        console.print(f"[dim]Use 'ami prompt-list' to see available prompts[/dim]")
        raise typer.Exit(1)
    except AMIBadRequest as e:
        console.print(f"[red]Error:[/red] Invalid request - {e}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-delete")
def prompt_delete(
    slug: str = typer.Argument(
        ...,
        help="Prompt slug to delete"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    force: bool = typer.Option(
        False,
        "--force", "-y",
        help="Skip confirmation"
    ),
) -> None:
    """
    Delete a prompt (soft delete).

    Examples:
        ami prompt-delete old-prompt -w dev -p my-project
        ami prompt-delete old-prompt --force
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    if not force:
        confirm = typer.confirm(f"Delete prompt '{slug}'?")
        if not confirm:
            console.print("[yellow]Cancelled[/yellow]")
            raise typer.Exit(0)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.delete_prompt(
            slug,
            workspace=workspace_resolved,
            project=project_resolved,
        )

        console.print(f"[green]OK[/green] Deleted prompt '{slug}'")

    except AMINotFound as e:
        console.print(f"[red]Error:[/red] Prompt '{slug}' not found")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-move")
def prompt_move(
    slug: str = typer.Argument(
        ...,
        help="Prompt slug to move"
    ),
    folder_path: Optional[str] = typer.Argument(
        None,
        help="Target folder path (omit or empty for root)"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
) -> None:
    """
    Move a prompt to a different folder.

    Examples:
        ami prompt-move welcome-email onboarding -w dev -p my-project
        ami prompt-move welcome-email archive/2024
        ami prompt-move welcome-email  # Move to root
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    from memrail.prompts.models import PromptMove

    move_data = PromptMove(folder_path=folder_path)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.move_prompt(
            slug,
            move_data,
            workspace=workspace_resolved,
            project=project_resolved,
        )

        target = folder_path or "/"
        console.print(f"[green]OK[/green] Moved prompt '{slug}' to '{target}'")

    except AMINotFound as e:
        console.print(f"[red]Error:[/red] Prompt '{slug}' not found")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-versions")
def prompt_versions(
    slug: str = typer.Argument(
        ...,
        help="Prompt slug"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    limit: int = typer.Option(
        20,
        "--limit", "-l",
        help="Maximum versions to return"
    ),
    offset: int = typer.Option(
        0,
        "--offset",
        help="Pagination offset"
    ),
    format: str = typer.Option(
        "table",
        "--format", "-f",
        help="Output format: table, json, yaml"
    ),
) -> None:
    """
    List versions of a prompt.

    Examples:
        ami prompt-versions welcome-email -w dev -p my-project
        ami prompt-versions welcome-email --format json
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.list_prompt_versions(
            slug,
            workspace=workspace_resolved,
            project=project_resolved,
            limit=limit,
            offset=offset,
        )

        versions = result.get("items", [])
        active_version = result.get("active_version", 1)
        # Latest version is the highest version number (first in list since sorted desc)
        latest_version = versions[0].get("version", 1) if versions else 1

        if format == "table":
            if not versions:
                console.print("[yellow]No versions found[/yellow]")
            else:
                _display_versions_table(versions, active_version, latest_version)
        else:
            _output_format(result, format)

    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-activate")
def prompt_activate(
    slug: str = typer.Argument(
        ...,
        help="Prompt slug"
    ),
    version: int = typer.Argument(
        ...,
        help="Version number to activate"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    force: bool = typer.Option(
        False,
        "--force", "-y",
        help="Skip confirmation"
    ),
) -> None:
    """
    Activate a specific version of a prompt (rollback).

    Examples:
        ami prompt-activate welcome-email 2 -w dev -p my-project
        ami prompt-activate welcome-email 1 --force
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    if not force:
        confirm = typer.confirm(f"Activate version {version} for prompt '{slug}'?")
        if not confirm:
            console.print("[yellow]Cancelled[/yellow]")
            raise typer.Exit(0)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.activate_prompt_version(
            slug,
            version,
            workspace=workspace_resolved,
            project=project_resolved,
        )

        previous = result.get("previous_version", "?")
        console.print(f"[green]OK[/green] Activated v{version} for prompt '{slug}' (was v{previous})")

    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-analytics")
def prompt_analytics(
    slug: str = typer.Argument(
        ...,
        help="Prompt slug"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    period: str = typer.Option(
        "7d",
        "--period",
        help="Time period: 24h, 7d, 30d, all"
    ),
    format: str = typer.Option(
        "panel",
        "--format", "-f",
        help="Output format: panel, json, yaml"
    ),
) -> None:
    """
    Get usage analytics for a prompt.

    Examples:
        ami prompt-analytics welcome-email -w dev -p my-project
        ami prompt-analytics welcome-email --period 30d
        ami prompt-analytics welcome-email --format json
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    if period not in ["24h", "7d", "30d", "all"]:
        console.print(f"[red]Error:[/red] Invalid period '{period}'. Use: 24h, 7d, 30d, or all")
        raise typer.Exit(1)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.get_prompt_analytics(
            slug,
            workspace=workspace_resolved,
            project=project_resolved,
            period=period,
        )

        if format == "panel":
            console.print(f"\n[bold]Analytics for '{slug}' ({period})[/bold]")
            _display_analytics_panel(result)
        else:
            _output_format(result, format)

    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-folder-list")
def prompt_folder_list(
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    parent_path: Optional[str] = typer.Option(
        None,
        "--parent",
        help="Parent path to list children of"
    ),
    format: str = typer.Option(
        "table",
        "--format", "-f",
        help="Output format: table, json, yaml"
    ),
) -> None:
    """
    List prompt folders in a project.

    Examples:
        ami prompt-folder-list -w dev -p my-project
        ami prompt-folder-list --parent onboarding
        ami prompt-folder-list --format json
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.list_prompt_folders(
            workspace=workspace_resolved,
            project=project_resolved,
            parent_path=parent_path,
        )

        if format == "table":
            if not result:
                console.print("[yellow]No folders found[/yellow]")
            else:
                _display_folders_table(result)
        else:
            _output_format({"folders": result}, format)

    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-folder-create")
def prompt_folder_create(
    path: str = typer.Argument(
        ...,
        help="Full folder path (e.g., 'support/tier-1')"
    ),
    name: str = typer.Argument(
        ...,
        help="Display name for the folder"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
) -> None:
    """
    Create a prompt folder.

    Parent folders are created automatically if they don't exist.

    Examples:
        ami prompt-folder-create onboarding "Onboarding" -w dev -p my-project
        ami prompt-folder-create support/tier-1 "Tier 1 Support"
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    from memrail.prompts.models import FolderCreate

    folder_data = FolderCreate(name=name, path=path)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.create_prompt_folder(
            folder_data,
            workspace=workspace_resolved,
            project=project_resolved,
        )

        console.print(f"[green]OK[/green] Created folder '{path}'")

    except AMIConflict as e:
        console.print(f"[red]Error:[/red] Folder '{path}' already exists")
        raise typer.Exit(1)
    except AMIBadRequest as e:
        console.print(f"[red]Error:[/red] Invalid folder path - {e}")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command("prompt-folder-delete")
def prompt_folder_delete(
    folder_path: str = typer.Argument(
        ...,
        help="Folder path to delete"
    ),
    org: Optional[str] = typer.Option(
        None,
        "--org", "-o",
        help="Organization slug"
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace slug or ID"
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project slug or ID"
    ),
    force: bool = typer.Option(
        False,
        "--force", "-y",
        help="Move contained prompts to root instead of failing"
    ),
) -> None:
    """
    Delete a prompt folder.

    By default, fails if the folder contains prompts.
    Use --force to move contained prompts to root.

    Examples:
        ami prompt-folder-delete old-folder -w dev -p my-project
        ami prompt-folder-delete archive/2023 --force
    """
    load_environment()

    workspace_resolved = workspace or os.environ.get("AMI_WORKSPACE")
    project_resolved = project or os.environ.get("AMI_PROJECT")

    if not workspace_resolved:
        console.print("[red]Error:[/red] Workspace required. Use --workspace or set AMI_WORKSPACE")
        raise typer.Exit(1)

    if not project_resolved:
        console.print("[red]Error:[/red] Project required. Use --project or set AMI_PROJECT")
        raise typer.Exit(1)

    client = get_ami_client(org=org, workspace=workspace_resolved, project=project_resolved)

    try:
        result = client.delete_prompt_folder(
            folder_path,
            workspace=workspace_resolved,
            project=project_resolved,
            force=force,
        )

        if force and result.get("prompts_moved", 0) > 0:
            console.print(f"[green]OK[/green] Deleted folder '{folder_path}' ({result['prompts_moved']} prompts moved to root)")
        else:
            console.print(f"[green]OK[/green] Deleted folder '{folder_path}'")

    except AMINotFound as e:
        console.print(f"[red]Error:[/red] Folder '{folder_path}' not found")
        raise typer.Exit(1)
    except AMIConflict as e:
        console.print(f"[red]Error:[/red] Folder '{folder_path}' contains prompts. Use --force to move them to root")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


# =============================================================================
# EMU SYNC COMMANDS
# =============================================================================

from cli.emu_sync import emu_pull, emu_plan, emu_apply, emu_diff, emu_validate

app.command("emu-pull")(emu_pull)
app.command("emu-plan")(emu_plan)
app.command("emu-apply")(emu_apply)
app.command("emu-diff")(emu_diff)
app.command("emu-validate")(emu_validate)


if __name__ == "__main__":
    app()
