"""Chat node for interactive conversation with Claude Code SDK."""

import asyncio
import json
import logging

# === SIGINT Isolation Patch ===
# Patch anyio.open_process to use start_new_session=True on Unix systems.
# This prevents SIGINT from propagating to Claude Code subprocess,
# allowing us to use client.interrupt() instead of reconnecting.
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Any

import anyio
from prompt_toolkit import PromptSession
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.history import History
from prompt_toolkit.styles import Style
from rich.console import Console
from rich.live import Live
from rich.spinner import Spinner

from fwauto_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient

from ..auth import get_auth_server_url, get_display_name, get_user_token, is_direct_api_mode
from ..chat_logger import ChatLogger
from ..logging_config import get_logger
from ..prompt_manager import PromptManager
from ..slash_commands import SlashCommandCompleter
from ..state import FirmwareState

_original_open_process = anyio.open_process


async def _patched_open_process(*args, **kwargs):
    """Patched open_process that isolates subprocess from parent's signal group."""
    if sys.platform != "win32":
        kwargs.setdefault("start_new_session", True)
    return await _original_open_process(*args, **kwargs)


anyio.open_process = _patched_open_process
# === End SIGINT Isolation Patch ===

logger = get_logger(__name__)
console = Console()

# Suppress SDK's "Fatal error" messages during SIGINT handling
# These are expected when Ctrl+C interrupts the SDK subprocess
logging.getLogger("fwauto_agent_sdk").setLevel(logging.CRITICAL)
logging.getLogger("fwauto_agent_sdk._internal").setLevel(logging.CRITICAL)
logging.getLogger("fwauto_agent_sdk._internal.query").setLevel(logging.CRITICAL)

# Global prompt manager
prompt_manager = PromptManager()


class ChatExitError(Exception):
    """Raised when user requests to exit chat via /exit command."""

    pass


class JsonHistory(History):
    """JSON-based history with timestamp support."""

    def __init__(self, filepath: Path) -> None:
        self.filepath = filepath
        super().__init__()

    def load_history_strings(self) -> list[str]:
        """Load history from JSON file."""
        if not self.filepath.exists():
            return []
        try:
            with open(self.filepath, encoding="utf-8") as f:
                data = json.load(f)
            # Return inputs in reverse order (most recent first, as required by prompt_toolkit)
            return [entry["input"] for entry in reversed(data)]
        except (json.JSONDecodeError, KeyError, OSError):
            return []

    def store_string(self, string: str) -> None:
        """Append new entry to JSON file."""
        # Load existing data
        data = []
        if self.filepath.exists():
            try:
                with open(self.filepath, encoding="utf-8") as f:
                    data = json.load(f)
            except (json.JSONDecodeError, OSError):
                data = []

        # Append new entry
        data.append(
            {
                "input": string,
                "timestamp": datetime.now().astimezone().isoformat(),
            }
        )

        # Write back
        try:
            with open(self.filepath, "w", encoding="utf-8") as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
        except OSError:
            pass  # Silent fail, don't block input


# ============================================
# Error Classification for AI Repair
# ============================================

# Errors that AI cannot fix (infrastructure/environment issues)
INFRASTRUCTURE_ERROR_PATTERNS = [
    # Network connectivity errors (raw SSH errors)
    "no route to host",
    "connection refused",
    "connection timed out",
    "network is unreachable",
    "host is down",
    "host unreachable",
    # Network connectivity errors (translated messages)
    "cannot reach",
    "not responding",
    "network unreachable",
    # SSH authentication errors
    "authentication failed",
    "permission denied (publickey",
    "too many authentication failures",
    "ssh authentication failed",
    # Hardware/connection errors
    "port not reachable",
    "ssh connection failed",
    "device not powered on",
    "not on same network",
]

# Errors that AI can help diagnose but usually not fix directly
CONFIG_ERROR_PATTERNS = [
    "config error",
    "deploy script not found",
]


def classify_error_for_ai_repair(command: str, error_output: str) -> tuple[bool, str, str]:
    """
    Classify error to determine if AI repair is appropriate.

    Args:
        command: The slash command that failed (e.g., "build", "deploy")
        error_output: The error message/output

    Returns:
        Tuple of (should_offer_ai_repair, prompt_message, ai_prompt_template)
        - should_offer_ai_repair: Whether to offer AI repair option
        - prompt_message: Message to show user explaining the situation
        - ai_prompt_template: Template for AI prompt if user accepts

    Classification logic:
    - build errors: Always offer AI repair (can fix code)
    - deploy infrastructure errors: Show troubleshooting tips, no AI repair
    - deploy config errors: Offer AI help for diagnosis only
    - other errors: Offer AI diagnosis
    """
    error_lower = error_output.lower()

    # Build command: AI can actually fix code errors
    if command == "build":
        ai_prompt = (
            f"The command /{command} failed. Error message:\n\n"
            f"{error_output}\n\n"
            "Please help me diagnose and fix the issue."
        )
        return (
            True,
            "Need AI help to diagnose and fix? (y/n)",
            ai_prompt,
        )

    # Deploy command: Check error type
    if command == "deploy":
        # Check for infrastructure errors (AI cannot fix)
        for pattern in INFRASTRUCTURE_ERROR_PATTERNS:
            if pattern in error_lower:
                tips = _get_infrastructure_troubleshooting_tips(error_output)
                return (
                    False,
                    f"[yellow]💡 Troubleshooting tips:[/yellow]\n{tips}",
                    "",
                )

        # Check for config errors (AI can help diagnose)
        for pattern in CONFIG_ERROR_PATTERNS:
            if pattern in error_lower:
                config_prompt = (
                    f"The command /{command} failed with a configuration error:\n\n"
                    f"{error_output}\n\n"
                    "Please help me understand and fix this configuration issue."
                )
                return (
                    True,
                    "This looks like a configuration issue. Need AI help to diagnose? (y/n)",
                    config_prompt,
                )

        # Other deploy errors (runtime errors, etc.) - AI can help diagnose
        other_prompt = (
            f"The command /{command} failed. Error message:\n\n{error_output}\n\nPlease help me diagnose the issue."
        )
        return (
            True,
            "Need AI help to diagnose? (y/n)",
            other_prompt,
        )

    # Default: offer AI diagnosis for other commands
    default_prompt = (
        f"The command /{command} failed. Error message:\n\n{error_output}\n\nPlease help me diagnose the issue."
    )
    return (
        True,
        "Need AI help to diagnose? (y/n)",
        default_prompt,
    )


def _get_infrastructure_troubleshooting_tips(error_output: str) -> str:
    """
    Generate troubleshooting tips based on infrastructure error type.

    Args:
        error_output: The error message

    Returns:
        Formatted troubleshooting tips string
    """
    error_lower = error_output.lower()
    tips = []

    # Network connectivity issues
    if any(p in error_lower for p in ["no route to host", "network is unreachable", "host unreachable"]):
        tips = [
            "1. Check if the board is powered on",
            "2. Verify the board is connected to the same network",
            "3. Try: ping <board_ip>",
            "4. Check the IP address in .fwauto/config.toml",
        ]
    elif "connection refused" in error_lower:
        tips = [
            "1. Check if SSH service is running on the board",
            "2. Verify SSH port (default: 22) is correct",
            "3. Try: ssh <user>@<board_ip>",
        ]
    elif "connection timed out" in error_lower or "host is down" in error_lower:
        tips = [
            "1. Check network connectivity",
            "2. Verify the board is powered on",
            "3. Check firewall settings",
            "4. Try: ping <board_ip>",
        ]
    # SSH authentication issues
    elif "authentication failed" in error_lower or "permission denied" in error_lower:
        tips = [
            "1. Verify SSH credentials in .fwauto/config.toml",
            "2. Check if SSH key is properly configured",
            "3. Try: ssh <user>@<board_ip> (manually test connection)",
            "4. Ensure the user account exists on the board",
        ]
    else:
        tips = [
            "1. Check network connectivity: ping <board_ip>",
            "2. Verify board is powered on and connected",
            "3. Check SSH credentials in .fwauto/config.toml",
        ]

    return "\n".join(f"   {tip}" for tip in tips)


def chat_node(state: FirmwareState) -> dict[str, Any]:
    """
    Interactive chat node using ClaudeSDKClient.

    This node:
    1. Loads the chat prompt template
    2. Configures Claude SDK with full permissions
    3. Enters interactive conversation loop
    4. Maintains conversation history in memory (via SDK)
    5. Exits on '/exit' or Ctrl+D

    Args:
        state: Current firmware state

    Returns:
        Updated state with chat_completed flag
    """
    logger.debug("💬 Chat: Starting interactive mode...")

    # Get project context from state
    project_root = state.get("project_root", "")

    # Load chat prompt template
    try:
        system_prompt = prompt_manager.get_prompt("chat", dict(state))
        logger.debug(f"💬 Chat: System prompt loaded ({len(system_prompt)} chars)")
    except Exception as e:
        logger.error(f"❌ Chat: Failed to load prompt: {e}")
        console.print(f"[red]❌ Failed to load chat prompt: {e}[/red]")
        return {**state, "chat_completed": False}

    # Configure API mode (direct vs proxy)
    env = {}

    if is_direct_api_mode():
        # Direct 模式：使用本機 Claude Max 訂閱，不設定 env
        logger.debug("💬 Chat: Using direct API (Claude Max subscription)")
    else:
        # Proxy 模式：使用 FWAuto Server LiteLLM proxy（OpenAI Chat Completions 格式）
        user_token = get_user_token()
        server_url = get_auth_server_url()

        if user_token and server_url:
            env = {
                "FWAUTO_BASE_URL": f"{server_url}/api",
                "FWAUTO_TOKEN": user_token,
            }
            logger.debug(f"💬 Chat: Using LiteLLM proxy at {server_url}/api")
        else:
            logger.warning("💬 Chat: No auth token found, using direct API (requires FWAUTO_TOKEN)")

    # Configure Agent SDK with full permissions
    # Read model from config, fallback to SDK default
    config = state.get("config")
    model_kwargs = {}
    if config and config.ai_model:
        model_kwargs["model"] = config.ai_model

    options = ClaudeAgentOptions(
        **model_kwargs,
        allowed_tools=[
            "Read",
            "Write",
            "Edit",
            "MultiEdit",
            "Glob",
            "Grep",
            "Bash",
            "WebFetch",
        ],
        permission_mode="bypassPermissions",
        add_dirs=[project_root] if project_root else [],
        cwd=project_root if project_root else None,
        source="chat",
        system_prompt=system_prompt,
        env=env,
        web_search=True,
    )

    logger.info(f"💬 Chat: Project root = {project_root}")
    console.print("\n[cyan]💬 Entering chat mode[/cyan]")
    if is_direct_api_mode():
        console.print("[green]  • API: Direct (Claude Max subscription)[/green]")
    console.print("[dim]  • Type /exit or Ctrl+D to quit[/dim]")
    console.print("[dim]  • Add \\ at end of line for multi-line input[/dim]\n")

    # Initialize loggers
    chat_logger = None
    cmd_logger = None
    if project_root:
        try:
            from ..command_logger import CommandLogger

            chat_logger = ChatLogger(Path(project_root), state)
            cmd_logger = CommandLogger(Path(project_root), state)
            logger.debug("💬 Chat: Loggers initialized")
        except Exception as e:
            logger.warning(f"⚠️ Chat: Failed to initialize loggers: {e}")

    # Execute async chat loop
    # Always use threading to avoid event loop conflicts
    # This is safer than trying to detect running loops, as some
    # libraries may create event loops unexpectedly
    import signal
    import threading

    result_container = {"completed": False, "error": None, "loop": None, "current_task": None}

    def run_in_thread():
        """Run chat loop in a separate thread with its own event loop."""
        try:
            new_loop = asyncio.new_event_loop()
            asyncio.set_event_loop(new_loop)
            result_container["loop"] = new_loop
            try:
                new_loop.run_until_complete(_run_chat_loop(options, chat_logger, cmd_logger, state, result_container))
                logger.debug("💬 Chat: _run_chat_loop completed normally")
                result_container["completed"] = True
            finally:
                result_container["loop"] = None
                new_loop.close()
        except ChatExitError:
            logger.debug("💬 Chat: ChatExitError caught in run_in_thread")
            result_container["completed"] = True
        except Exception as e:
            logger.debug(f"💬 Chat: Exception in run_in_thread: {type(e).__name__}: {e}")
            result_container["error"] = e

    thread: threading.Thread | None = None

    # Save original SIGINT handler
    original_sigint_handler = signal.getsignal(signal.SIGINT)

    def sigint_handler(signum, frame):
        """Handle SIGINT (Ctrl+C) by cancelling the current async task."""
        loop = result_container.get("loop")
        task = result_container.get("current_task")
        if loop is not None and task is not None and not task.done():
            loop.call_soon_threadsafe(task.cancel)
            logger.debug("💬 Chat: SIGINT received, cancelling current task")
        else:
            logger.debug("💬 Chat: SIGINT received, no active task to cancel")

    try:
        logger.debug("💬 Chat: Starting chat loop in dedicated thread")
        thread = threading.Thread(target=run_in_thread, name="ChatLoopThread", daemon=False)
        thread.start()

        # Install custom SIGINT handler while thread is running
        signal.signal(signal.SIGINT, sigint_handler)

        while thread.is_alive():
            thread.join(timeout=0.5)

        if result_container["error"]:
            raise result_container["error"]

        logger.info("✅ Chat: Session ended normally")
        logger.debug(f"💬 Chat: result_container = {result_container}")
        return {**state, "chat_completed": result_container["completed"]}
    except KeyboardInterrupt:
        # This shouldn't happen with our custom handler, but handle it gracefully
        logger.debug("💬 Chat: KeyboardInterrupt escaped to main handler")
        if thread is not None and thread.is_alive():
            thread.join(timeout=3)
        if result_container["completed"]:
            return {**state, "chat_completed": True}
        raise
    except Exception as e:
        logger.error(f"❌ Chat: Session failed: {e}")
        console.print(f"[red]❌ Chat error: {e}[/red]")
        return {**state, "chat_completed": False}
    finally:
        # Restore original SIGINT handler
        signal.signal(signal.SIGINT, original_sigint_handler)


async def _run_chat_loop(
    options: ClaudeAgentOptions,
    chat_logger: ChatLogger | None = None,
    cmd_logger: Any | None = None,
    state: FirmwareState | None = None,
    result_container: dict | None = None,
) -> None:
    """
    Execute async interactive chat loop.

    Args:
        options: Claude Agent SDK options
        chat_logger: Optional chat logger for conversation recording
        cmd_logger: Optional command logger for slash command recording
        state: Current FirmwareState (needed for command execution)
        result_container: Shared dict for SIGINT handler to cancel current task
    """
    # Get display name for prompt
    display_name = get_display_name()

    # Setup persistent input history
    history = None
    project_root = state.get("project_root") if state else None
    if project_root:
        history_dir = Path(project_root) / ".fwauto" / "history"
        history_dir.mkdir(parents=True, exist_ok=True)
        history_file = history_dir / "chat_history.json"
        history = JsonHistory(history_file)
        logger.debug("Chat history file: %s", history_file)

    # Create async prompt session for CJK support
    prompt_style = Style.from_dict({"cyan": "#00ffff bold"})
    session = PromptSession(
        message=HTML(f"<cyan><b>{display_name}></b></cyan> "),
        style=prompt_style,
        history=history,
        completer=SlashCommandCompleter(),
    )

    # Check for initial message from CLI
    pending_input = None
    if state:
        cmd_args = state.get("command_args") or {}
        pending_input = cmd_args.get("initial_message")

    # Track if we need to reconnect SDK after interrupt
    client = None
    need_reconnect = True

    try:
        while True:
            # Connect/reconnect SDK if needed
            if need_reconnect:
                if client is not None:
                    try:
                        await client.disconnect()
                    except Exception:
                        pass
                    try:
                        await client.__aexit__(None, None, None)
                    except Exception:
                        pass

                client = ClaudeSDKClient(options=options)
                await client.__aenter__()
                connect_start = time.time()
                await client.connect()
                connect_elapsed = time.time() - connect_start
                logger.debug(f"💬 Chat: SDK connected, elapsed={connect_elapsed:.2f}s")
                need_reconnect = False

            # 使用 pending_input 或等待用戶輸入
            if pending_input:
                user_input = pending_input
                console.print(f"[cyan]{display_name}>[/cyan] {user_input}")
                pending_input = None  # 清除，下次迴圈正常等待輸入
            else:
                # 多行輸入累積
                input_lines = []

                while True:
                    try:
                        if input_lines:
                            # 續行提示
                            line = await session.prompt_async(HTML("<cyan><b>...></b></cyan> "))
                        else:
                            # 首行提示
                            line = await session.prompt_async()
                    except EOFError:
                        console.print("[yellow]👋 Chat ended[/yellow]")
                        return  # Exit the function
                    except KeyboardInterrupt:
                        console.print()
                        input_lines = []  # 清空累積
                        break  # 跳出內層循環，重新開始

                    if line.endswith("\\"):
                        input_lines.append(line[:-1])  # 移除反斜線，繼續累積
                    else:
                        input_lines.append(line)
                        break  # 輸入完成

                if not input_lines:
                    continue  # Ctrl+C 清空後重新開始

                user_input = "\n".join(input_lines).strip()

            # Skip empty input
            if not user_input:
                continue

            # Process @ file references
            from ..at_reference import build_enriched_query, parse_at_references

            at_refs = parse_at_references(user_input, project_root or "")
            query_to_send = build_enriched_query(user_input, at_refs) if at_refs else user_input

            # Check for slash command
            from ..slash_commands import parse_slash_command

            slash_cmd = parse_slash_command(user_input)

            if slash_cmd:
                # Execute slash command
                assert state is not None and client is not None
                try:
                    await _handle_slash_command(slash_cmd, state, client, cmd_logger, chat_logger)
                except ChatExitError:
                    break  # Exit chat loop on /exit command
                continue  # Skip normal AI query

            # Log user message (normal conversation)
            if chat_logger:
                chat_logger.log_user_message(user_input)

            # Send message to Claude with spinner
            response_texts = []
            live = None
            query_start_time = time.time()

            async def _do_query_and_receive():
                """Coroutine for query+receive, cancellable via task.cancel()."""
                nonlocal live

                # Initial spinner
                spinner = Spinner("dots", text="[cyan]🤔 Thinking... (Ctrl+C to interrupt)[/cyan]")
                live_inner = Live(spinner, console=console, transient=True)
                live_inner.start()
                live = live_inner

                assert client is not None
                logger.debug(f"💬 Chat: Sending query ({len(query_to_send)} chars)")
                await client.query(query_to_send)
                query_sent_time = time.time()
                logger.debug(f"💬 Chat: Query sent, elapsed={query_sent_time - query_start_time:.2f}s")

                from fwauto_agent_sdk.types import AssistantMessage, TextBlock

                first_response = True
                pending_slash_cmds = []

                async for msg in client.receive_response():
                    if isinstance(msg, AssistantMessage):
                        for block in msg.content:
                            if isinstance(block, TextBlock):
                                if first_response:
                                    first_response_time = time.time()
                                    elapsed = first_response_time - query_start_time
                                    logger.debug(f"💬 Chat: First response received, elapsed={elapsed:.2f}s")
                                    first_response = False
                                live_inner.stop()
                                text = block.text.strip()
                                console.print(f"💬 {text}")
                                response_texts.append(block.text)

                                for line in text.split("\n"):
                                    line = line.strip()
                                    if line.startswith("/"):
                                        slash_cmd = parse_slash_command(line)
                                        if slash_cmd:
                                            pending_slash_cmds.append(slash_cmd)

                                spinner = Spinner("dots", text="[cyan]🤔 Thinking... (Ctrl+C to interrupt)[/cyan]")
                                live_inner = Live(spinner, console=console, transient=True)
                                live_inner.start()
                                live = live_inner

                live_inner.stop()
                live = None

                # Execute slash commands from AI response
                assert state is not None and client is not None
                for slash_cmd in pending_slash_cmds:
                    try:
                        console.print(f"\n[cyan]⚡ Executing: /{slash_cmd.name}[/cyan]")
                        await _handle_slash_command(slash_cmd, state, client, cmd_logger, chat_logger)
                    except ChatExitError:
                        break
                    except Exception as e:
                        console.print(f"[red]❌ Command failed: {e}[/red]")

                response_end_time = time.time()
                logger.debug(f"💬 Chat: Response complete, total_elapsed={response_end_time - query_start_time:.2f}s")

            try:
                query_task = asyncio.create_task(_do_query_and_receive())
                if result_container is not None:
                    result_container["current_task"] = query_task
                await query_task
            except asyncio.CancelledError:
                if live:
                    live.stop()
                console.print("\n[yellow]⚠️ Response interrupted[/yellow]")
                need_reconnect = True
                logger.debug("💬 Chat: Query cancelled by SIGINT")
            except Exception as e:
                if live:
                    live.stop()
                error_str = str(e).lower()
                if "exit code -2" in error_str or "exit code: -2" in error_str:
                    logger.warning(f"💬 Chat: Unexpected SIGINT propagation to subprocess: {e}")
                    console.print("\n[yellow]⚠️ Response interrupted[/yellow]")
                    need_reconnect = True
                else:
                    logger.warning(f"💬 Chat: Query error: {e}")
                    console.print(f"\n[yellow]⚠️ Response error: {e}[/yellow]")
                    need_reconnect = True
            finally:
                if result_container is not None:
                    result_container["current_task"] = None

            # Log assistant response
            if chat_logger and response_texts:
                chat_logger.log_assistant_message("\n".join(response_texts))

            console.print()  # Add spacing between exchanges

    finally:
        # Finalize logs
        if chat_logger:
            chat_logger.finalize()
        if cmd_logger:
            cmd_logger.finalize()

        # Ensure proper cleanup
        if client is not None:
            try:
                await client.disconnect()
                logger.debug("💬 Chat: SDK disconnected")
            except Exception:
                pass
            try:
                await client.__aexit__(None, None, None)
            except Exception:
                pass


async def _handle_slash_command(
    cmd: Any,
    state: FirmwareState,
    client: ClaudeSDKClient,
    cmd_logger: Any | None,
    chat_logger: Any | None,
) -> None:
    """Handle slash command execution.

    Steps:
    1. Parse command arguments
    2. Build appropriate FirmwareState
    3. Execute corresponding node (synchronously)
    4. Inject result into AI conversation (as system message)
    5. Log command execution
    6. If failed, ask user about AI repair

    Args:
        cmd: Parsed SlashCommand
        state: Current FirmwareState
        client: ClaudeSDKClient for AI conversation
        cmd_logger: Optional command logger
        chat_logger: Optional chat logger
    """
    from ..cli import _build_deploy_state, _build_log_state
    from ..slash_commands import parse_command_args

    try:
        # Parse arguments
        parsed_args = parse_command_args(cmd)
        cmd.parsed_args = parsed_args

        # Build state and execute based on command type
        if cmd.name == "build":
            from ..nodes.build import build_node

            exec_state: FirmwareState = {
                **state,
                "execution_mode": "build",
                "command": "build",
                "command_args": {},
            }
            result = build_node(exec_state)
            status = result["build_status"]
            output = result["build_log"]

        elif cmd.name == "deploy":
            from ..nodes.deploy import deploy_node

            # Handle scenario loading if needed
            binary_args = parsed_args.get("binary_args", "")
            scenario = parsed_args.get("scenario", "")

            if not binary_args and scenario:
                # Load scenario
                try:
                    from ..config import find_project_root, load_project_config

                    project_root = find_project_root()
                    if project_root:
                        config = load_project_config(project_root)
                        scenario_manager = config.get_scenario_manager()
                        scenario_obj = scenario_manager.get_scenario(scenario)
                        if scenario_obj:
                            binary_args = scenario_obj.binary_args
                            console.print(f"[green]✓[/green] Using scenario: {scenario_obj.name}")
                except Exception as e:
                    logger.warning(f"Failed to load scenario: {e}")

            exec_state = _build_deploy_state(binary_args, state["project_root"])
            result = deploy_node(exec_state)
            status = result["deploy_status"]
            output = result["deploy_log"]

        elif cmd.name == "log":
            from ..nodes.log import log_node

            log_path = parsed_args["log_path"]
            analysis_prompt = parsed_args["analysis_prompt"]
            exec_state = _build_log_state(log_path, analysis_prompt, state["project_root"])
            result = log_node(exec_state)
            output = result.get("log_analysis_result", "Analysis failed")

            # 檢測 log 未配置，自動觸發配置 wizard
            if output and "Log not configured" in output:
                from ..cli_commands.config import run_log_wizard

                config_path = Path(state["project_root"]) / ".fwauto" / "config.toml"
                if run_log_wizard(config_path):
                    # 配置成功，重新執行 log 分析
                    exec_state = _build_log_state(log_path, analysis_prompt, state["project_root"])
                    result = log_node(exec_state)
                    output = result.get("log_analysis_result", "Analysis failed")

            # Check if output is an error message
            status = "error" if output.startswith("Error:") else "success"

        elif cmd.name == "auth":
            subcommand = parsed_args.get("subcommand", "help")

            if subcommand == "status":
                from ..auth import cli_status

                cli_status()
                output = "Displayed auth status"
            elif subcommand == "logout":
                from ..auth import get_auth_manager

                auth = get_auth_manager()
                auth.logout()
                output = "Logged out"
            else:
                # 顯示 auth 子命令幫助
                console.print("\n[cyan]🔐 Auth Commands:[/cyan]")
                console.print("  /auth status  - Show authentication status")
                console.print("  /auth logout  - Logout and clear token")
                console.print()
                output = "Displayed auth help"

            # Log command execution
            if cmd_logger:
                cmd_logger.log_command_execution(
                    command=f"/auth {subcommand}" if subcommand != "help" else "/auth",
                    parameters=parsed_args,
                    status="success",
                    output=output,
                )
            return  # auth 不需要注入 AI 對話

        elif cmd.name == "config":
            from ..cli_commands.config import config_get, config_set, config_show

            subcommand = parsed_args.get("subcommand", "show")
            config_path = Path(state["project_root"]) / ".fwauto" / "config.toml"
            output = ""

            if subcommand == "show":
                config_show(config_path)
                output = "Displayed config"

            elif subcommand == "get":
                key = parsed_args["key"]
                try:
                    _, value = config_get(key, config_path)
                    if value is not None:
                        console.print(f"  {key} = {value}")
                    else:
                        console.print(f"[yellow]⚠️ Key '{key}' not found[/yellow]")
                except ValueError as e:
                    console.print(f"[red]❌ {e}[/red]")
                output = f"config get {key}"

            elif subcommand == "set":
                key = parsed_args["key"]
                value = parsed_args["value"]
                try:
                    config_set(key, value, config_path)
                    console.print(f"[green]✅ Set {key} = {value}[/green]")

                    # Reload config in state
                    from ..config import load_project_config

                    state["config"] = load_project_config(Path(state["project_root"]))
                except ValueError as e:
                    console.print(f"[red]❌ {e}[/red]")
                output = f"config set {key}={value}"

            # Log command execution
            if cmd_logger:
                cmd_logger.log_command_execution(
                    command=f"/config {subcommand}",
                    parameters=parsed_args,
                    status="success",
                    output=output,
                )
            return  # config 不需要注入 AI 對話

        elif cmd.name == "rag":
            subcommand = parsed_args.get("subcommand", "help")

            if subcommand == "list":
                from ..cli import _rag_interactive_list

                _rag_interactive_list()
            elif subcommand == "search":
                query = parsed_args.get("query", "")
                if query:
                    from ..cli import _get_rag_client, _rag_do_search

                    _rag_do_search(_get_rag_client(), query)
                else:
                    console.print("[yellow]Usage: /rag search <query>[/yellow]")
            else:
                console.print("\n[cyan]📚 RAG Commands:[/cyan]")
                console.print("  /rag list            - List all documents")
                console.print("  /rag search <query>  - Search documents")
                console.print()

            if cmd_logger:
                cmd_logger.log_command_execution(
                    command=f"/rag {subcommand}",
                    parameters=parsed_args,
                    status="success",
                    output=f"RAG {subcommand}",
                )
            return  # rag 不需要注入 AI 對話

        elif cmd.name == "help":
            from ..cli import _get_help_info

            help_text = _get_help_info()
            console.print(help_text)

            # Log command execution
            if cmd_logger:
                cmd_logger.log_command_execution(
                    command="/help",
                    parameters={},
                    status="success",
                    output=help_text,
                )
            return  # help 不需要注入 AI 對話

        elif cmd.name == "exit":
            console.print("[yellow]👋 Chat ended[/yellow]")
            # Log command execution
            if cmd_logger:
                cmd_logger.log_command_execution(
                    command="/exit",
                    parameters={},
                    status="success",
                    output="User requested exit",
                )
            raise ChatExitError  # Signal to exit the chat loop

        else:
            raise ValueError(f"Unknown command: {cmd.name}")

        # Log command execution
        if cmd_logger:
            cmd_logger.log_command_execution(
                command=f"/{cmd.name} {' '.join(cmd.raw_args)}",
                parameters=parsed_args,
                status=status,
                output=output,
            )

        # Inject result into AI conversation as system message (only on success)
        # When command fails, skip AI injection to avoid confusing output
        # (AI might analyze the error but status remains "error", showing "failed")
        if status == "success":
            system_msg = f"""[System] User executed command: /{cmd.name}
Parameters: {parsed_args}

Result: {status}

Full Log:
{output}
"""

            await client.query(system_msg)

            # Wait for AI to process and respond to the system message
            from fwauto_agent_sdk.types import AssistantMessage, TextBlock

            ai_response_texts = []
            async for msg in client.receive_response():
                if isinstance(msg, AssistantMessage):
                    for block in msg.content:
                        if isinstance(block, TextBlock):
                            text = block.text.strip()
                            if text:
                                console.print(f"💬 {text}")
                                ai_response_texts.append(block.text)

            # Log AI response to slash command result
            if chat_logger and ai_response_texts:
                chat_logger.log_assistant_message("\n".join(ai_response_texts))

        # Display result to user
        if status == "success":
            console.print(f"\n[green]✅ /{cmd.name} completed successfully[/green]")
            if output:
                console.print(f"[dim]{output}[/dim]")
        else:
            console.print(f"\n[red]❌ /{cmd.name} failed[/red]")
            if output:
                console.print(f"\n[yellow]Error details:[/yellow]\n{output}")

        # If failed, classify error and handle accordingly
        if status == "error":
            should_offer_ai, prompt_message, ai_prompt_template = classify_error_for_ai_repair(cmd.name, output or "")

            if should_offer_ai:
                # Offer AI repair option
                console.print(f"\n[yellow]{prompt_message}[/yellow]")

                # Get user confirmation
                from prompt_toolkit import PromptSession

                confirm_session = PromptSession()

                try:
                    confirm = await confirm_session.prompt_async("AI help (y/n): ")
                    confirm = confirm.strip().lower()

                    if confirm in ["y", "yes"]:
                        # Let AI analyze the error
                        if chat_logger:
                            chat_logger.log_user_message(ai_prompt_template)

                        await client.query(ai_prompt_template)

                        # Receive AI response
                        from fwauto_agent_sdk.types import AssistantMessage, TextBlock

                        response_texts = []
                        async for msg in client.receive_response():
                            from ..ai_utils import print_ai_message

                            print_ai_message(msg, prefix="💬")
                            if isinstance(msg, AssistantMessage):
                                for block in msg.content:
                                    if isinstance(block, TextBlock):
                                        response_texts.append(block.text)

                        if chat_logger and response_texts:
                            chat_logger.log_assistant_message("\n".join(response_texts))
                    else:
                        console.print("[cyan]AI help skipped[/cyan]")

                except (EOFError, KeyboardInterrupt):
                    console.print("[cyan]AI help skipped[/cyan]")
            else:
                # Infrastructure error - show troubleshooting tips directly
                console.print(f"\n{prompt_message}")

    except ChatExitError:
        raise  # Re-raise to signal exit to caller
    except Exception as e:
        console.print(f"[red]❌ Command execution error: {e}[/red]")
        logger.error(f"Slash command execution failed: {e}")

        if cmd_logger:
            cmd_logger.log_command_execution(
                command=f"/{cmd.name} {' '.join(cmd.raw_args)}",
                parameters=cmd.parsed_args,
                status="error",
                output=str(e),
            )
