"""
Agent Orchestrator - Main Entry Point

This module provides the main entry point for the agent orchestrator.
It initializes the system, sets up logging, and starts the orchestration loop.

The orchestrator uses Claude Code as the primary "brain" with a specialized
system prompt that enables it to coordinate multiple AI agents.

Usage:
    python -m agent_orchestrator
    # or
    agent-orchestrator  # if installed via pip
"""

import asyncio
import logging
import signal
import sys
import uuid
from pathlib import Path
from typing import Optional

from .config import get_config, Config
from .persistence.database import OrchestratorDB
from .persistence.models import Task
from .journal.project_journal import ProjectJournal
from .secrets.redactor import SecretRedactor
from .control.loop import AgentControlLoop
from .orchestrator.brain import OrchestrationBrain
from .tracking.cli_usage import get_cli_tracker, set_cli_tracker, CLIUsageTracker
from .cli.colors import fmt, style, Theme, Color, Formatter
from .cli.menu import CommandMenu, show_command_menu
from .cli.interactive import get_interactive_session, interactive_prompt


def setup_logging(config: Config) -> logging.Logger:
    """Set up logging with optional secret redaction."""
    logger = logging.getLogger("agent_orchestrator")
    logger.setLevel(getattr(logging, config.logging.level.upper()))

    # Console handler
    handler = logging.StreamHandler()
    handler.setLevel(logging.INFO)

    # Format
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    handler.setFormatter(formatter)

    # Wrap with secret redactor if enabled
    if config.logging.redact_secrets:
        original_emit = handler.emit

        def redacting_emit(record):
            record.msg = SecretRedactor.redact(str(record.msg))
            original_emit(record)

        handler.emit = redacting_emit

    logger.addHandler(handler)

    # File handler if configured
    if config.logging.file:
        file_handler = logging.FileHandler(config.logging.file)
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)

    return logger


class Orchestrator:
    """
    Main orchestrator class.

    Coordinates multiple agents, manages tasks, and enforces policies.
    Uses the OrchestrationBrain for intelligent task assignment and monitoring.
    """

    def __init__(self, config: Optional[Config] = None):
        """Initialize the orchestrator."""
        self.config = config or get_config()
        self.logger = setup_logging(self.config)
        self.db = OrchestratorDB(self.config.database.path)
        self.journal = ProjectJournal(Path.cwd())

        # Initialize ops path
        ops_path = Path.cwd() / "ops"
        ops_path.mkdir(parents=True, exist_ok=True)

        # Initialize CLI usage tracker FIRST (before brain, which caches a reference)
        set_cli_tracker(CLIUsageTracker())

        # Initialize the control loop
        self.control_loop = AgentControlLoop(
            db=self.db,
            ops_path=ops_path
        )

        # Initialize the orchestration brain (uses get_cli_tracker() internally)
        self.brain = OrchestrationBrain(
            db=self.db,
            ops_path=ops_path,
            workspace_root=Path.cwd(),
        )

        self.adapters = {}
        self._running = False

    def register_adapter(self, agent_id: str, adapter) -> None:
        """Register an agent adapter."""
        self.adapters[agent_id] = adapter
        self.control_loop.register_adapter(agent_id, adapter)
        self.brain.register_adapter(agent_id, adapter)

        # Initialize session in CLI tracker so agent shows as AVAILABLE
        tracker = get_cli_tracker()
        tracker.start_session(agent_id)

        self.logger.info(f"Registered adapter: {agent_id} ({adapter.get_name()})")

    async def start(self):
        """Start the orchestrator."""
        self.logger.info("Starting Agent Orchestrator...")
        self._running = True

        # Initialize project state if needed
        state = self.journal.read_state()
        self.logger.info(f"Project state version: {state.get('version', 'unknown')}")

        # Start the background control loop
        await self.control_loop.start()

        # Display colored startup banner
        self._print_startup_banner()

        # Main loop with interactive prompt
        # Get interactive session for command completion
        session = get_interactive_session()

        while self._running:
            try:
                # Use interactive prompt with command completion
                # When user types "/", autocomplete menu appears automatically
                task_desc = await session.prompt_async(
                    message=[
                        ('class:prompt', 'orchestrator'),
                        ('', ' '),
                        ('class:prompt', '❯'),
                        ('', ' '),
                    ],
                )

                task_desc = task_desc.strip()
                if not task_desc:
                    continue

                if task_desc.lower() in ("/exit", "/quit", "exit", "quit"):
                    print(fmt.info("Shutting down..."))
                    break

                if task_desc.startswith("/"):
                    await self._handle_command(task_desc)
                else:
                    await self._handle_user_input(task_desc)

            except asyncio.CancelledError:
                # Loop cancelled (e.g. by Ctrl+C handled in main)
                break
            except EOFError:
                # Ctrl+D
                break
            except Exception as e:
                self.logger.error(f"Error in main loop: {SecretRedactor.redact(str(e))}")
                print(fmt.error(str(e)))
                import traceback
                traceback.print_exc()
                await asyncio.sleep(1)

        await self.shutdown()

    def _print_startup_banner(self) -> None:
        """Print the colored startup banner."""
        status = self.brain.get_system_status()

        print()
        print(fmt.box_top(64))
        print(fmt.box_line(fmt.header("AGENT ORCHESTRATOR"), 64))
        print(fmt.box_line("", 64))

        # Project info
        project = status.get("project", {})
        project_line = f"{style('Project:', Theme.TEXT_DIM)} {style(project.get('name', 'Unknown'), Theme.PRIMARY)}"
        print(fmt.box_line(project_line, 64))

        phase_line = f"{style('Phase:', Theme.TEXT_DIM)} {style(project.get('phase', 'Unknown'), Theme.SECONDARY)}"
        print(fmt.box_line(phase_line, 64))

        version_line = f"{style('Version:', Theme.TEXT_DIM)} {project.get('version', '0.0.0')}"
        print(fmt.box_line(version_line, 64))

        print(fmt.box_bottom(64))
        print()

        # Agent status section
        print(fmt.header("  REGISTERED AGENTS"))
        print(fmt.divider("─", 64))

        agents = status.get("agents", {}).get("registered", [])
        if agents:
            for agent in agents:
                agent_id = agent.get("id", "unknown")
                availability = agent.get("availability", "unknown")
                session_pct = agent.get("session_percentage", 0)

                # Status badge
                badge = fmt.status_badge(availability)

                # Agent name
                name = fmt.agent(agent_id)

                # Progress bar
                bar = fmt.progress_bar(session_pct, width=15)

                print(f"  {badge:<25} {name:<20} {bar}")
        else:
            print(fmt.dim("  No agents registered"))

        # Spawned agents
        spawned = status.get("agents", {}).get("spawned", [])
        if spawned:
            print()
            print(fmt.subheader("  Spawned Specialists"))
            for agent in spawned:
                print(f"  {fmt.dim('•')} {fmt.agent(agent['id'])} ({agent['type']}) - {agent['state']}")

        print()

        # Task summary
        tasks = status.get("tasks", {})
        pending = tasks.get("pending", 0)
        in_progress = tasks.get("in_progress", 0)

        task_line = f"  {style('Tasks:', Theme.TEXT_DIM)} {pending} pending, {in_progress} in progress"
        print(task_line)

        # Show running task hint if tasks are in progress
        if in_progress > 0:
            print()
            print(f"  {style('→', Theme.ACCENT)} Use {fmt.command('/progress')} to see task status")
            print(f"  {style('→', Theme.ACCENT)} Use {fmt.command('/cancel')} <task-id> to stop a task")

        # Warnings
        warnings = status.get("warnings", [])
        if warnings:
            print()
            for warning in warnings:
                print(f"  {fmt.warning(warning)}")

        print()
        print(fmt.divider("─", 64))
        hint = f"  Type {fmt.command('/')} to see available commands"
        print(hint)
        print()

    async def _handle_command(self, command_str: str):
        """Handle an orchestrator command through the brain."""
        # Use the brain's command handler for orchestrator commands
        result = await self.brain.process_command(command_str)

        print()
        if result.success:
            print(fmt.success(result.message))

            # Special handling for /view --attach
            if result.data.get("action") == "attach":
                agent_id = result.data.get("agent_id")
                print()
                print(style(f"  Tip: Press Ctrl+B then D to detach and return here", Theme.TEXT_DIM))
                print()
                # Actually attach to the tmux session
                import subprocess
                subprocess.run(["tmux", "attach-session", "-t", agent_id])
                # After detaching, user returns here
                print()
                print(fmt.success(f"Returned from {agent_id} session"))
            elif result.data.get("output"):
                # Special handling for captured output
                print()
                print(style("─" * 60, Theme.TEXT_DIM))
                print(result.data["output"])
                print(style("─" * 60, Theme.TEXT_DIM))
                if result.data.get("tip"):
                    print(style(f"  Tip: {result.data['tip']}", Theme.TEXT_DIM))
            elif result.data:
                self._print_data(result.data)
        else:
            print(fmt.error(result.message))

        if result.suggestions:
            print()
            print(style("Suggestions:", Theme.TEXT_DIM))
            for suggestion in result.suggestions:
                print(f"  {style('→', Theme.ACCENT)} {suggestion}")
        print()

    def _print_data(self, data: dict, indent: int = 0) -> None:
        """Pretty print command result data with colors."""
        prefix = "  " * indent
        for key, value in data.items():
            key_styled = style(f"{key}:", Theme.SECONDARY)
            if isinstance(value, dict):
                print(f"{prefix}{key_styled}")
                self._print_data(value, indent + 1)
            elif isinstance(value, list):
                print(f"{prefix}{key_styled}")
                for item in value[:10]:  # Limit list output
                    if isinstance(item, dict):
                        for k, v in item.items():
                            k_styled = style(k, Theme.TEXT_DIM)
                            print(f"{prefix}  {k_styled}: {v}")
                        print()
                    else:
                        print(f"{prefix}  {style('•', Theme.ACCENT)} {item}")
            else:
                # Special formatting for certain keys
                if key in ("agent_id", "id"):
                    value = fmt.agent(str(value))
                elif key in ("task_id",):
                    value = fmt.task(str(value))
                elif key == "availability":
                    value = fmt.status_badge(str(value))
                print(f"{prefix}{key_styled} {value}")

    async def _handle_user_input(self, description: str):
        """Handle a user-submitted task using the brain's intelligence."""
        # Check for project creation intent FIRST
        if self._is_project_creation_intent(description):
            await self._interactive_project_wizard(description)
            return

        # Let the brain suggest the best agent
        agent_id, reason = self.brain.suggest_agent_for_task(description)

        if agent_id.startswith("spawn:"):
            # Brain suggests spawning a specialist - offer planning phase
            agent_type = agent_id.split(":")[1]
            await self._offer_planning_phase(description, agent_type, reason)
            return

        if not agent_id:
            print()
            print(fmt.warning(f"No available agents: {reason}"))
            print(f"  Consider starting more agents or checking {fmt.command('/health')}")
            return

        # Auto-assign to suggested agent
        print()
        arrow = style("→", Theme.ACCENT)
        print(f"{arrow} Assigning to {fmt.agent(agent_id)}: {style(reason, Theme.TEXT_DIM)}")

        # Use the brain's command to assign
        result = await self.brain.process_command(f"/assign {agent_id} {description}")

        if result.success:
            print(fmt.success(result.message))
            if "task_id" in result.data:
                print(f"  {style('Task ID:', Theme.TEXT_DIM)} {fmt.task(result.data['task_id'])}")
        else:
            print(fmt.error(result.message))

            # Fallback: create task without assignment
            task_id = f"task-{uuid.uuid4().hex[:8]}"
            task = Task(
                id=task_id,
                description=description,
                task_type="general",
                status="pending"
            )
            self.db.create_task(task)
            print(f"  Created unassigned task: {fmt.task(task_id)}")

    def _is_project_creation_intent(self, text: str) -> bool:
        """
        Detect if the user wants to create/start a new project.

        This catches phrases like:
        - "I want to create a project"
        - "create a new project"
        - "start a project"
        - "new project"
        - "help me plan a project"
        """
        import re
        text_lower = text.lower().strip()

        # Direct project creation patterns
        project_patterns = [
            r'\b(create|start|begin|make|new|setup|set up)\s+(a\s+)?(new\s+)?project\b',
            r'\bnew\s+project\b',
            r'\bproject\s+(creation|setup|planning)\b',
            r'\b(want|like|need)\s+to\s+(create|start|begin|make)\s+(a\s+)?(new\s+)?project\b',
            r'\bhelp\s+(me\s+)?(create|start|plan|setup)\s+(a\s+)?(new\s+)?project\b',
            r'\blet\'?s\s+(create|start|begin|make)\s+(a\s+)?(new\s+)?project\b',
            r'^create\s+project\b',
            r'^new\s+project\b',
        ]

        for pattern in project_patterns:
            if re.search(pattern, text_lower):
                return True

        return False

    async def _interactive_project_wizard(self, initial_input: str):
        """
        Interactive wizard for creating a new project.

        Asks the user questions to understand:
        1. What the project is about
        2. Project name
        3. Storage location
        4. Goals and requirements
        """
        print()
        print(fmt.header("  NEW PROJECT WIZARD"))
        print(fmt.divider("─", 60))
        print()
        print(f"  {style('Let me help you set up a new project.', Theme.TEXT_DIM)}")
        print()

        session = get_interactive_session()

        # Step 1: What is the project about?
        print(f"  {style('1.', Theme.ACCENT)} {style('What is this project about?', Theme.SECONDARY)}")
        print(f"     {style('Describe what you want to build or create:', Theme.TEXT_DIM)}")
        print()

        project_description = await session.prompt_async(
            message=[
                ('class:prompt', '   Description'),
                ('', ' '),
                ('class:prompt', '❯'),
                ('', ' '),
            ],
        )
        project_description = project_description.strip()

        if not project_description:
            print()
            print(fmt.warning("Project creation cancelled - no description provided."))
            return

        # Step 2: Analyze the task to suggest a name and team
        print()
        print(f"  {style('Analyzing your project...', Theme.TEXT_DIM)}")

        try:
            from .intelligence import analyze_task
            from .agents.templates import get_registry

            analysis = analyze_task(project_description)
            registry = get_registry()
            team = registry.compose_team(analysis)

            domain_name = analysis.primary_domain.value.replace("_", " ").title()
            confidence = analysis.primary_confidence

            # Generate suggested name
            words = project_description.lower().split()[:3]
            suggested_name = "-".join(w for w in words if len(w) > 2)[:30]

        except Exception as e:
            self.logger.error(f"Analysis error: {e}")
            domain_name = "General"
            confidence = 0.5
            team = None
            suggested_name = "my-project"

        print()
        print(f"  {style('Domain detected:', Theme.TEXT_DIM)} {style(domain_name, Theme.PRIMARY)} ({confidence:.0%} confidence)")
        print()

        # Step 3: Project name
        print(f"  {style('2.', Theme.ACCENT)} {style('What would you like to call this project?', Theme.SECONDARY)}")
        print(f"     {style(f'Suggested: {suggested_name}', Theme.TEXT_DIM)}")
        print(f"     {style('Press Enter to accept, or type a custom name:', Theme.TEXT_DIM)}")
        print()

        project_name = await session.prompt_async(
            message=[
                ('class:prompt', '   Name'),
                ('', ' '),
                ('class:prompt', '❯'),
                ('', ' '),
            ],
        )
        project_name = project_name.strip() or suggested_name

        # Step 4: Show the proposed team
        print()
        print(f"  {style('3.', Theme.ACCENT)} {style('Recommended team for this project:', Theme.SECONDARY)}")
        print()

        if team and team.lead:
            print(f"     {style('Lead:', Theme.TEXT_DIM)} {style(team.lead.name, Theme.AGENT_NAME)} ({team.lead.id})")
            print(f"            {style(team.lead.description[:60], Theme.TEXT_DIM)}...")

            if team.specialists:
                print()
                print(f"     {style('Specialists:', Theme.TEXT_DIM)}")
                for spec in team.specialists:
                    print(f"       • {style(spec.name, Theme.AGENT_NAME)}: {spec.description[:40]}...")

            if team.reviewers:
                print()
                print(f"     {style('Reviewers:', Theme.TEXT_DIM)}")
                for rev in team.reviewers:
                    print(f"       • {style(rev.name, Theme.AGENT_NAME)}")
        else:
            print(f"     {style('No specific team recommended - will use available agents', Theme.TEXT_DIM)}")

        # Step 5: Confirm and create
        print()
        print(fmt.divider("─", 60))
        print()
        print(f"  {style('Ready to create project:', Theme.SECONDARY)}")
        print(f"     {style('Name:', Theme.TEXT_DIM)} {style(project_name, Theme.PRIMARY)}")
        print(f"     {style('Domain:', Theme.TEXT_DIM)} {domain_name}")
        print(f"     {style('Description:', Theme.TEXT_DIM)} {project_description[:50]}...")
        print()

        confirm = await session.prompt_async(
            message=[
                ('class:prompt', '   Create this project? (yes/no)'),
                ('', ' '),
                ('class:prompt', '❯'),
                ('', ' '),
            ],
        )

        if confirm.strip().lower() in ('yes', 'y'):
            # Create the project
            print()
            print(fmt.info("Creating project..."))

            result = await self.brain.process_command(f"/project new {project_description}")

            if result.success:
                print(fmt.success(f"Project '{project_name}' created!"))
                print()

                # Offer to spawn agents
                if team and team.lead:
                    spawn_agents = await session.prompt_async(
                        message=[
                            ('class:prompt', f'   Spawn {team.lead.name} to start working? (yes/no)'),
                            ('', ' '),
                            ('class:prompt', '❯'),
                            ('', ' '),
                        ],
                    )

                    if spawn_agents.strip().lower() in ('yes', 'y'):
                        spawn_result = await self.brain.process_command(f"/spawn {team.lead.id}")
                        if spawn_result.success:
                            agent_id = spawn_result.data.get("agent_id", "")
                            print(fmt.success(f"Spawned {team.lead.name}: {agent_id}"))

                            # Assign the task
                            if agent_id:
                                assign_result = await self.brain.process_command(
                                    f"/assign {agent_id} {project_description}"
                                )
                                if assign_result.success:
                                    print(fmt.success(f"Task assigned to {agent_id}"))
                        else:
                            print(fmt.warning(f"Could not spawn agent: {spawn_result.message}"))
            else:
                print(fmt.error(f"Failed to create project: {result.message}"))
        else:
            print()
            print(fmt.info("Project creation cancelled."))
            print(f"  You can always use {fmt.command('/project new <description>')} later.")

    async def _offer_planning_phase(self, description: str, agent_type: str, reason: str):
        """
        Offer an interactive planning phase when specialist agent is needed.

        This creates a project plan and asks the user for confirmation before
        spawning agents and starting work.
        """
        print()
        print(fmt.header("  PROJECT PLANNING"))
        print(fmt.divider("─", 50))
        print()

        # Try to use the project planning system
        try:
            result = await self.brain.process_command(f"/project new {description}")

            if result.success:
                # Display the plan
                print(result.message)
                print()

                # Show options
                print(fmt.divider("─", 50))
                print()
                print(f"  {style('Options:', Theme.SECONDARY)}")
                print(f"    {fmt.command('yes')} / {fmt.command('y')} - Create project and spawn agents")
                print(f"    {fmt.command('no')} / {fmt.command('n')} - Cancel and try a different approach")
                print(f"    {fmt.command('/spawn')} {agent_type} - Manually spawn just the agent")
                print()

                # Get user response
                session = get_interactive_session()
                response = await session.prompt_async(
                    message=[
                        ('class:prompt', 'Proceed?'),
                        ('', ' '),
                        ('class:prompt', '❯'),
                        ('', ' '),
                    ],
                )

                response = response.strip().lower()

                if response in ('yes', 'y'):
                    # Execute the plan
                    await self._execute_project_plan(result.data.get("plan", {}), description)
                elif response.startswith('/spawn'):
                    # User wants manual spawn
                    await self._handle_command(response)
                else:
                    print()
                    print(fmt.info("Planning cancelled. You can:"))
                    print(f"  • Try a different task description")
                    print(f"  • Use {fmt.command('/spawn')} to manually create agents")
                    print(f"  • Use {fmt.command('/agent list')} to see available templates")
            else:
                # Planning failed, fall back to simple spawn suggestion
                self._show_simple_spawn_suggestion(agent_type, reason)

        except Exception as e:
            self.logger.error(f"Planning phase error: {e}")
            self._show_simple_spawn_suggestion(agent_type, reason)

    def _show_simple_spawn_suggestion(self, agent_type: str, reason: str):
        """Show a simple spawn suggestion without full planning."""
        print()
        print(fmt.info(f"Recommendation: {reason}"))
        print(f"  Would spawn {style(agent_type, Theme.AGENT_NAME)} specialist.")
        print(f"  Use {fmt.command('/spawn')} command to create the specialist first.")
        print(f"  Example: {fmt.command(f'/spawn {agent_type}')}")

    async def _execute_project_plan(self, plan: dict, description: str):
        """Execute a project plan by creating the project and spawning agents."""
        print()
        print(fmt.info("Creating project and spawning agents..."))

        lead_agent = plan.get("lead_agent", "")
        team_size = plan.get("team_size", 1)

        # Spawn the lead agent
        if lead_agent:
            result = await self.brain.process_command(f"/spawn {lead_agent}")
            if result.success:
                print(fmt.success(f"Spawned lead agent: {result.data.get('agent_id', lead_agent)}"))

                # Assign the task to the spawned agent
                agent_id = result.data.get("agent_id", "")
                if agent_id:
                    assign_result = await self.brain.process_command(
                        f"/assign {agent_id} {description}"
                    )
                    if assign_result.success:
                        print(fmt.success(f"Task assigned: {assign_result.data.get('task_id', 'unknown')}"))
                    else:
                        print(fmt.warning(f"Could not assign task: {assign_result.message}"))
            else:
                print(fmt.error(f"Failed to spawn agent: {result.message}"))
        else:
            print(fmt.warning("No lead agent specified in plan"))

    async def shutdown(self):
        """Gracefully shut down the orchestrator."""
        self.logger.info("Shutting down Agent Orchestrator...")
        self._running = False

        # Shutdown brain (terminates spawned agents)
        await self.brain.shutdown()

        # Stop control loop
        await self.control_loop.stop()

        # Cancel all running agents
        for agent_id, adapter in self.adapters.items():
            try:
                await adapter.cancel()
                self.logger.info(f"Cancelled agent: {agent_id}")
            except Exception as e:
                self.logger.error(f"Error cancelling {agent_id}: {e}")

        self.logger.info("Shutdown complete")

    def stop(self):
        """Request graceful shutdown."""
        self._running = False


async def async_main():
    """Async main function."""
    # Create and start orchestrator
    orchestrator = Orchestrator()

    # Register adapters based on available API keys
    config = get_config()

    # CLI Agents (Control Plane A) - Registered first as primary
    try:
        from .adapters.claude_code_cli import create_claude_code_adapter
        adapter = create_claude_code_adapter(
            agent_id="claude-code",
            workspace_path=str(Path.cwd())
        )
        # Check if auth works
        if await adapter.check_authentication():
            orchestrator.register_adapter("claude-code", adapter)
            orchestrator.logger.info("CLI auth ok: claude-code")
        else:
            orchestrator.logger.warning(f"CLI auth failed: claude-code ({adapter.auth_error()})")
    except ImportError:
        pass

    try:
        from .adapters.gemini_cli import create_gemini_adapter
        adapter = create_gemini_adapter(
            agent_id="gemini-cli",
            workspace_path=str(Path.cwd())
        )
        if await adapter.check_authentication():
            orchestrator.register_adapter("gemini-cli", adapter)
            orchestrator.logger.info("CLI auth ok: gemini-cli")
        else:
            orchestrator.logger.warning(f"CLI auth failed: gemini-cli ({adapter.auth_error()})")
    except ImportError:
        pass

    try:
        from .adapters.codex_cli import create_codex_adapter
        adapter = create_codex_adapter(
            agent_id="codex-cli",
            workspace_path=str(Path.cwd()),
            autonomy_mode="auto-edit"
        )
        if await adapter.check_authentication():
            orchestrator.register_adapter("codex-cli", adapter)
            orchestrator.logger.info("CLI auth ok: codex-cli")
        else:
            orchestrator.logger.warning(f"CLI auth failed: codex-cli ({adapter.auth_error()})")
    except ImportError:
        pass

    # API Agents (Control Plane B) - Fallback/Specialized
    if config.api_keys.has_anthropic():
        try:
            from .adapters.claude_sdk import ClaudeSDKAdapter

            adapter = ClaudeSDKAdapter(
                agent_id="claude-main",
                api_key=config.api_keys.anthropic_api_key,
            )
            orchestrator.register_adapter("claude-main", adapter)
        except ImportError:
            orchestrator.logger.warning("Claude SDK not installed, skipping")

    if config.api_keys.has_openai():
        try:
            from .adapters.openai_agents import OpenAIAgentsAdapter

            adapter = OpenAIAgentsAdapter(
                agent_id="openai-main",
                api_key=config.api_keys.openai_api_key,
            )
            orchestrator.register_adapter("openai-main", adapter)
        except ImportError:
            orchestrator.logger.warning("OpenAI SDK not installed, skipping")

    try:
        await orchestrator.start()
    except asyncio.CancelledError:
        # Occurs when asyncio.run cancels the task on KeyboardInterrupt
        pass
    finally:
        # Ensure cleanup happens even on cancellation
        await orchestrator.shutdown()


def main():
    """Main entry point."""
    # Print colored startup message
    title = style("Agent Orchestrator", Theme.PRIMARY, Color.BOLD)
    version = style("v0.1.0", Theme.TEXT_DIM)
    print(f"\n{title} {version}")
    print(fmt.divider("═", 40))

    try:
        asyncio.run(async_main())
    except KeyboardInterrupt:
        # Standard Ctrl+C handling
        # asyncio.run() will have already cancelled tasks and exited
        print(f"\n{fmt.info('Interrupted')}")
        sys.exit(0)


if __name__ == "__main__":
    main()
