use std::io::{BufRead, BufReader, Write};
use std::process::{Child, ChildStdin, Command, Stdio};
use std::sync::Arc;
use std::thread;
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::{AppHandle, Emitter};
use tempfile::NamedTempFile;

#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;

use crate::achievements::{get_achievement_info, AchievementUnlockedEvent};
use crate::commands::record_cost;
use crate::config::ClaudeStartOptions;
use crate::stats::{calculate_cost, StatsUpdateEvent, UsageStats};
use crate::types::{
    AgentEndEvent, AgentStartEvent, CharacterState, ClaudeMessage, ConnectionEvent,
    ConnectionStatus, ContentBlock, MessageCost, OutputEvent, PermissionPromptEvent,
    PermissionPromptEventItem, QuestionOption, SessionEvent, StateChangeEvent, TodoItem,
    TodoUpdateEvent, UserQuestionEvent, WorkingDirectoryEvent,
};
use parking_lot::RwLock;
use std::cell::RefCell;

thread_local! {
    /// Stores pending tool uses from the most recent Assistant message
    /// to enable batching permission requests for sibling cancelled tools
    static PENDING_TOOL_USES: RefCell<Vec<PendingToolUse>> = const { RefCell::new(Vec::new()) };
}

#[derive(Debug, Clone)]
struct PendingToolUse {
    tool_use_id: String,
    tool_name: String,
    tool_input: serde_json::Value,
}

const SEARCH_TOOLS: [&str; 5] = ["Read", "Glob", "Grep", "WebSearch", "WebFetch"];
const CODING_TOOLS: [&str; 3] = ["Edit", "Write", "NotebookEdit"];

fn detect_wsl() -> bool {
    // Check /proc/version for WSL indicators
    if let Ok(version) = std::fs::read_to_string("/proc/version") {
        let version_lower = version.to_lowercase();
        if version_lower.contains("microsoft") || version_lower.contains("wsl") {
            return true;
        }
    }

    // Fallback: check for WSLInterop
    if std::path::Path::new("/proc/sys/fs/binfmt_misc/WSLInterop").exists() {
        return true;
    }

    // Check for WSL environment variable
    if std::env::var("WSL_DISTRO_NAME").is_ok() {
        return true;
    }

    false
}

fn find_claude_binary() -> Option<String> {
    // Check common installation locations for claude
    let home = std::env::var("HOME").ok()?;
    let paths_to_check = [
        format!("{}/.local/bin/claude", home),
        format!("{}/.claude/local/claude", home),
        "/usr/local/bin/claude".to_string(),
        "/usr/bin/claude".to_string(),
    ];

    for path in &paths_to_check {
        if std::path::Path::new(path).exists() {
            return Some(path.clone());
        }
    }

    // Fall back to checking PATH via which
    if let Ok(output) = Command::new("which").arg("claude").output() {
        if output.status.success() {
            let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
            if !path.is_empty() {
                return Some(path);
            }
        }
    }

    None
}

pub struct WslBridge {
    process: Option<Child>,
    stdin: Option<ChildStdin>,
    working_directory: String,
    session_id: Option<String>,
    mcp_config_file: Option<NamedTempFile>,
    stats: Arc<RwLock<UsageStats>>,
    conversation_id: Option<String>,
}

impl WslBridge {
    pub fn new() -> Self {
        WslBridge {
            process: None,
            stdin: None,
            working_directory: String::new(),
            session_id: None,
            mcp_config_file: None,
            stats: Arc::new(RwLock::new(UsageStats::new())),
            conversation_id: None,
        }
    }

    pub fn new_with_conversation_id(conversation_id: String) -> Self {
        WslBridge {
            process: None,
            stdin: None,
            working_directory: String::new(),
            session_id: None,
            mcp_config_file: None,
            stats: Arc::new(RwLock::new(UsageStats::new())),
            conversation_id: Some(conversation_id),
        }
    }

    pub fn start(&mut self, app: AppHandle, options: ClaudeStartOptions) -> Result<(), String> {
        if self.process.is_some() {
            return Err("Process already running".to_string());
        }

        // Load saved achievements and stats when starting a new session
        let app_clone = app.clone();
        let stats = self.stats.clone();
        tauri::async_runtime::spawn(async move {
            tracing::info!("Loading saved achievements...");
            let achievements = crate::achievements::load_achievements(&app_clone).await;
            tracing::info!(
                "Loaded {} unlocked achievements",
                achievements.unlocked.len()
            );

            tracing::info!("Loading saved stats...");
            let persisted_stats = crate::stats::load_stats(&app_clone).await;

            let mut stats_guard = stats.write();
            stats_guard.achievements = achievements;

            if let Some(persisted) = persisted_stats {
                tracing::info!("Applying persisted lifetime stats");
                stats_guard.apply_persisted(persisted);
            }
        });

        let working_dir = &options.working_dir;
        self.working_directory = working_dir.clone();

        emit_connection_status(
            &app,
            ConnectionStatus::Connecting,
            self.conversation_id.clone(),
        );

        // Create temp file for MCP config if provided
        let mcp_config_path = if let Some(ref mcp_json) = options.mcp_servers_json {
            if !mcp_json.trim().is_empty() {
                // Validate JSON before writing
                serde_json::from_str::<serde_json::Value>(mcp_json)
                    .map_err(|e| format!("Invalid MCP servers JSON: {}", e))?;

                let mut temp_file = NamedTempFile::new()
                    .map_err(|e| format!("Failed to create temp file for MCP config: {}", e))?;
                temp_file
                    .write_all(mcp_json.as_bytes())
                    .map_err(|e| format!("Failed to write MCP config: {}", e))?;
                temp_file
                    .flush()
                    .map_err(|e| format!("Failed to flush MCP config: {}", e))?;

                let path = temp_file.path().to_string_lossy().to_string();
                self.mcp_config_file = Some(temp_file);
                Some(path)
            } else {
                None
            }
        } else {
            None
        };

        // Detect if we're running inside WSL or on Windows
        let is_wsl = detect_wsl();
        tracing::debug!("is_wsl: {}", is_wsl);
        tracing::debug!("options: {:?}", options);

        let mut command = if is_wsl {
            // Running inside WSL - call claude directly
            // Try to find claude in common locations since GUI apps may not inherit shell PATH
            let claude_path = find_claude_binary().ok_or_else(|| {
                "Could not find claude binary. Is Claude Code installed?".to_string()
            })?;

            tracing::debug!("Found claude at: {}", claude_path);
            tracing::debug!("Working dir: {}", working_dir);

            let mut cmd = Command::new(&claude_path);
            cmd.args([
                "--output-format",
                "stream-json",
                "--input-format",
                "stream-json",
                "--verbose",
                "--debug",
                "hooks",
            ]);

            // Add model if specified
            if let Some(ref model) = options.model {
                if !model.is_empty() {
                    cmd.args(["--model", model]);
                }
            }

            // Add allowed tools if any
            for tool in &options.allowed_tools {
                cmd.args(["--allowedTools", tool]);
            }

            // Add custom instructions as system prompt if specified
            if let Some(ref instructions) = options.custom_instructions {
                if !instructions.is_empty() {
                    cmd.args(["--system-prompt", instructions]);
                }
            }

            // Add MCP config if provided
            if let Some(ref mcp_path) = mcp_config_path {
                cmd.args(["--mcp-config", mcp_path]);
            }

            // Add resume flag if session ID provided
            if let Some(ref session_id) = options.resume_session_id {
                if !session_id.is_empty() {
                    cmd.args(["--resume", session_id]);
                }
            }

            cmd.current_dir(working_dir);

            // Set API key as environment variable if specified
            if let Some(ref api_key) = options.api_key {
                if !api_key.is_empty() {
                    cmd.env("ANTHROPIC_API_KEY", api_key);
                }
            }

            cmd
        } else {
            // Running on Windows - use wsl with bash login shell to ensure PATH is loaded
            tracing::debug!("Windows path - using wsl");
            let mut cmd = Command::new("wsl");

            // Build the claude command with all arguments
            let mut claude_cmd = format!("cd '{}' && ", working_dir);

            // Set API key as environment variable if specified
            if let Some(ref api_key) = options.api_key {
                if !api_key.is_empty() {
                    claude_cmd.push_str(&format!("ANTHROPIC_API_KEY='{}' ", api_key));
                }
            }

            claude_cmd.push_str(
                "claude --output-format stream-json --input-format stream-json --verbose",
            );

            // Add model if specified
            if let Some(ref model) = options.model {
                if !model.is_empty() {
                    claude_cmd.push_str(&format!(" --model '{}'", model));
                }
            }

            // Add allowed tools if any
            for tool in &options.allowed_tools {
                claude_cmd.push_str(&format!(" --allowedTools '{}'", tool));
            }

            // Add custom instructions as system prompt if specified
            if let Some(ref instructions) = options.custom_instructions {
                if !instructions.is_empty() {
                    // Escape single quotes in instructions
                    let escaped = instructions.replace('\'', "'\\''");
                    claude_cmd.push_str(&format!(" --system-prompt '{}'", escaped));
                }
            }

            // Add MCP config if provided
            if let Some(ref mcp_path) = mcp_config_path {
                claude_cmd.push_str(&format!(" --mcp-config '{}'", mcp_path));
            }

            // Add resume flag if session ID provided
            if let Some(ref session_id) = options.resume_session_id {
                if !session_id.is_empty() {
                    claude_cmd.push_str(&format!(" --resume '{}'", session_id));
                }
            }

            // Use bash -lc to load login profile (ensures PATH includes claude)
            cmd.args(["-e", "bash", "-lc", &claude_cmd]);

            // Hide the console window on Windows
            #[cfg(target_os = "windows")]
            cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW

            cmd
        };

        command
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());

        let mut child = command.spawn().map_err(|e| {
            tracing::error!("Spawn error: {:?}", e);
            format!("Failed to spawn process: {}", e)
        })?;

        let stdin = child.stdin.take();
        let stdout = child.stdout.take();
        let stderr = child.stderr.take();

        self.stdin = stdin;
        self.process = Some(child);

        // Note: We no longer reset stats here - stats persist across reconnects
        // Stats are only reset when explicitly disconnecting via stop()

        // Load saved achievements
        let app_handle = app.clone();
        let stats_clone = self.stats.clone();
        tokio::spawn(async move {
            let saved_progress = crate::achievements::load_achievements(&app_handle).await;
            stats_clone.write().achievements = saved_progress;
        });

        if let Some(stdout) = stdout {
            let app_clone = app.clone();
            let stats_clone = self.stats.clone();
            let conv_id = self.conversation_id.clone();
            thread::spawn(move || {
                handle_stdout(stdout, app_clone, stats_clone, conv_id);
            });
        }

        if let Some(stderr) = stderr {
            let app_clone = app.clone();
            let conv_id = self.conversation_id.clone();
            thread::spawn(move || {
                handle_stderr(stderr, app_clone, conv_id);
            });
        }

        emit_connection_status(
            &app,
            ConnectionStatus::Connected,
            self.conversation_id.clone(),
        );

        Ok(())
    }

    pub fn send_message(&mut self, message: &str) -> Result<(), String> {
        let stdin = self.stdin.as_mut().ok_or("Process not running")?;

        // Track input for cost estimation on interrupt
        {
            let mut stats = self.stats.write();
            stats.current_request_input = Some(message.to_string());
            stats.current_request_output_chars = 0;
            stats.current_request_thinking_chars = 0;
            stats.current_request_tools.clear();
        }

        let input = serde_json::json!({
            "type": "user",
            "message": {
                "role": "user",
                "content": [{
                    "type": "text",
                    "text": message
                }]
            }
        });

        let json_line = serde_json::to_string(&input).map_err(|e| e.to_string())?;

        stdin
            .write_all(format!("{}\n", json_line).as_bytes())
            .map_err(|e| format!("Failed to write to stdin: {}", e))?;

        stdin
            .flush()
            .map_err(|e| format!("Failed to flush stdin: {}", e))?;

        Ok(())
    }

    pub fn send_tool_result(
        &mut self,
        tool_use_id: &str,
        result: serde_json::Value,
    ) -> Result<(), String> {
        let stdin = self.stdin.as_mut().ok_or("Process not running")?;

        // The content should be a JSON string representation of the result
        let content_str = serde_json::to_string(&result).map_err(|e| e.to_string())?;

        let input = serde_json::json!({
            "type": "user",
            "message": {
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,
                    "content": content_str
                }]
            }
        });

        let json_line = serde_json::to_string(&input).map_err(|e| e.to_string())?;

        stdin
            .write_all(format!("{}\n", json_line).as_bytes())
            .map_err(|e| format!("Failed to write to stdin: {}", e))?;

        stdin
            .flush()
            .map_err(|e| format!("Failed to flush stdin: {}", e))?;

        Ok(())
    }

    pub fn interrupt(&mut self, app: &AppHandle) -> Result<(), String> {
        // Due to persistent bug in Claude Code where ESC/Ctrl+C doesn't work,
        // we have to kill the process. This is the only reliable way to stop it.
        // See: https://github.com/anthropics/claude-code/issues/3455
        if let Some(mut process) = self.process.take() {
            // Estimate cost for interrupted request before killing
            self.estimate_interrupted_request_cost(app);

            // Kill the process immediately
            let _ = process.kill();
            let _ = process.wait();

            // Clear stdin
            self.stdin = None;

            // Clear tracking fields
            {
                let mut stats = self.stats.write();
                stats.current_request_input = None;
                stats.current_request_output_chars = 0;
                stats.current_request_thinking_chars = 0;
                stats.current_request_tools.clear();
            }

            // Keep session_id and working directory for user reference
            // The user will see what session was interrupted

            // Emit disconnected status
            emit_connection_status(
                app,
                ConnectionStatus::Disconnected,
                self.conversation_id.clone(),
            );

            Ok(())
        } else {
            Err("No active process to interrupt".to_string())
        }
    }

    fn estimate_interrupted_request_cost(&mut self, app: &AppHandle) {
        // Read tracking data from stats
        let (input_chars, output_chars, thinking_chars, tools, model) = {
            let stats = self.stats.read();

            // Only estimate if we have tracked content
            if stats.current_request_input.is_none()
                && stats.current_request_output_chars == 0
                && stats.current_request_thinking_chars == 0
                && stats.current_request_tools.is_empty() {
                return;
            }

            let input_chars = stats.current_request_input.as_ref().map(|s| s.len() as u64).unwrap_or(0);
            let model = stats.model.clone().unwrap_or_else(|| "claude-sonnet-4-5-20250929".to_string());

            (input_chars, stats.current_request_output_chars, stats.current_request_thinking_chars, stats.current_request_tools.clone(), model)
        };

        tracing::info!("[COST ESTIMATION] Estimating cost for interrupted request");

        // Use conservative 3.5 chars/token for estimation (vs standard 4)
        let estimated_input_tokens = (input_chars as f64 / 3.5).ceil() as u64;
        let estimated_output_tokens = ((output_chars as f64 / 3.5).ceil() as u64)
            + ((thinking_chars as f64 / 3.5).ceil() as u64);

        // Add tool overhead based on session averages
        let mut tool_overhead_tokens = 0u64;
        {
            let stats = self.stats.read();
            for tool_name in &tools {
                if let Some(tool_stats) = stats.session_tools_usage.get(tool_name) {
                    if tool_stats.call_count > 0 {
                        // Use session average tokens per call for this tool
                        let avg_tokens = (tool_stats.estimated_input_tokens + tool_stats.estimated_output_tokens)
                            / tool_stats.call_count;
                        tool_overhead_tokens += avg_tokens;
                        tracing::info!("[COST ESTIMATION] Tool {} average: {} tokens", tool_name, avg_tokens);
                    }
                }
            }
        }

        let total_estimated_input = estimated_input_tokens + tool_overhead_tokens;
        let total_estimated_output = estimated_output_tokens;

        // Add 20% safety margin to overestimate
        let safety_margin = 1.2;
        let conservative_input = (total_estimated_input as f64 * safety_margin).ceil() as u64;
        let conservative_output = (total_estimated_output as f64 * safety_margin).ceil() as u64;

        tracing::info!("[COST ESTIMATION] Input: {} chars → {} tokens (+ {} tool overhead) × 1.2 safety = {} tokens",
            input_chars, estimated_input_tokens, tool_overhead_tokens, conservative_input);
        tracing::info!("[COST ESTIMATION] Output: {} chars → {} tokens × 1.2 safety = {} tokens",
            output_chars + thinking_chars,
            estimated_output_tokens, conservative_output);

        // Calculate cost (no cache tokens for interrupted requests)
        let estimated_cost = calculate_cost(
            conservative_input,
            conservative_output,
            &model,
            None,
            None,
        );

        tracing::info!("[COST ESTIMATION] Estimated cost: ${:.4} (conservative)", estimated_cost);

        // Add to stats with estimated flag
        {
            let mut stats_guard = self.stats.write();
            stats_guard.add_usage(
                conservative_input,
                conservative_output,
                &model,
                None,
                None,
            );
        }

        // Emit stats update
        let stats_update_event = StatsUpdateEvent {
            stats: self.stats.read().clone(),
        };
        let _ = app.emit("claude:stats", stats_update_event);

        // Record to historical cost tracking (mark as estimated)
        let app_clone = app.clone();
        tauri::async_runtime::spawn(async move {
            record_cost(&app_clone, conservative_input, conservative_output, estimated_cost).await;
        });
    }

    pub fn stop(&mut self, app: &AppHandle) {
        if let Some(mut process) = self.process.take() {
            let _ = process.kill();
            let _ = process.wait();
        }
        self.stdin = None;
        self.session_id = None;
        self.mcp_config_file = None; // Temp file is automatically deleted when dropped

        // Save lifetime stats before resetting session
        let stats_snapshot = self.stats.read().clone();
        let app_clone = app.clone();
        tauri::async_runtime::spawn(async move {
            tracing::info!("Saving stats on session stop...");
            if let Err(e) = crate::stats::save_stats(&app_clone, &stats_snapshot).await {
                tracing::error!("Failed to save stats: {}", e);
            } else {
                tracing::info!("Stats saved successfully on session stop");
            }
        });

        // Reset session stats on explicit disconnect
        self.stats.write().reset_session();

        emit_connection_status(
            app,
            ConnectionStatus::Disconnected,
            self.conversation_id.clone(),
        );
    }

    pub fn is_running(&self) -> bool {
        self.process.is_some()
    }

    pub fn get_working_directory(&self) -> &str {
        &self.working_directory
    }

    pub fn get_stats(&self) -> UsageStats {
        self.stats.read().clone()
    }
}

impl Default for WslBridge {
    fn default() -> Self {
        Self::new()
    }
}

fn handle_stdout(
    stdout: std::process::ChildStdout,
    app: AppHandle,
    stats: Arc<RwLock<UsageStats>>,
    conversation_id: Option<String>,
) {
    let reader = BufReader::new(stdout);

    for line in reader.lines() {
        match line {
            Ok(line) if !line.is_empty() => {
                if let Err(e) = process_json_line(&line, &app, &stats, &conversation_id) {
                    tracing::error!("Error processing line: {}", e);
                }
            }
            Err(e) => {
                tracing::error!("Error reading stdout: {}", e);
                break;
            }
            _ => {}
        }
    }

    emit_connection_status(&app, ConnectionStatus::Disconnected, conversation_id);
}

fn handle_stderr(
    stderr: std::process::ChildStderr,
    app: AppHandle,
    conversation_id: Option<String>,
) {
    let reader = BufReader::new(stderr);

    for line in reader.lines() {
        match line {
            Ok(line) if !line.is_empty() => {
                // Check if this is a SubagentStart hook message
                if line.contains("[SubagentStart Hook]") {
                    if let Some(agent_data) = parse_subagent_start_hook(&line) {
                        tracing::debug!("Parsed SubagentStart hook: agent_id={}, parent={:?}",
                            agent_data.agent_id, agent_data.parent_tool_use_id);

                        // Emit an agent-update event with the agent_id
                        let _ = app.emit(
                            "claude:agent-update",
                            serde_json::json!({
                                "conversationId": conversation_id.clone(),
                                "toolUseId": agent_data.parent_tool_use_id,
                                "agentId": agent_data.agent_id,
                            }),
                        );
                    }
                }

                // Still emit the stderr line as output
                let _ = app.emit(
                    "claude:output",
                    OutputEvent {
                        line_type: "error".to_string(),
                        content: line,
                        tool_name: None,
                        conversation_id: conversation_id.clone(),
                        cost: None,
                        parent_tool_use_id: None,
                    },
                );
            }
            Err(_) => break,
            _ => {}
        }
    }
}

#[derive(Debug)]
struct SubagentStartData {
    agent_id: String,
    parent_tool_use_id: Option<String>,
}

fn parse_subagent_start_hook(line: &str) -> Option<SubagentStartData> {
    // Parse: [SubagentStart Hook] agent_id=agent-xxx, parent_tool_use_id=Some("toolu_xxx"), ...

    // Extract agent_id
    let agent_id = line
        .split("agent_id=")
        .nth(1)?
        .split(',')
        .next()?
        .trim()
        .to_string();

    // Extract parent_tool_use_id if present
    let parent_tool_use_id = if line.contains("parent_tool_use_id=Some") {
        line.split("parent_tool_use_id=Some(\"")
            .nth(1)?
            .split('"')
            .next()
            .map(|s| s.to_string())
    } else {
        None
    };

    Some(SubagentStartData {
        agent_id,
        parent_tool_use_id,
    })
}

fn process_json_line(
    line: &str,
    app: &AppHandle,
    stats: &Arc<RwLock<UsageStats>>,
    conversation_id: &Option<String>,
) -> Result<(), String> {
    let message: ClaudeMessage = serde_json::from_str(line)
        .map_err(|e| format!("Failed to parse JSON: {} - Line: {}", e, line))?;

    match &message {
        ClaudeMessage::System {
            subtype,
            session_id,
            cwd,
            ..
        } => {
            if subtype == "init" {
                if let Some(id) = session_id {
                    let _ = app.emit(
                        "claude:session",
                        SessionEvent {
                            session_id: id.clone(),
                            conversation_id: conversation_id.clone(),
                        },
                    );
                }
                if let Some(dir) = cwd {
                    let _ = app.emit(
                        "claude:cwd",
                        WorkingDirectoryEvent {
                            directory: dir.clone(),
                            conversation_id: conversation_id.clone(),
                        },
                    );
                }
                emit_state_change(app, CharacterState::Idle, None, conversation_id.clone());
            }
        }

        ClaudeMessage::Assistant { message, parent_tool_use_id } => {
            let mut state = CharacterState::Typing;
            let mut tool_name = None;

            // Collect all tool names in this message for token attribution
            let tools_in_message: Vec<String> = message
                .content
                .iter()
                .filter_map(|block| match block {
                    ContentBlock::ToolUse { name, .. } => Some(name.clone()),
                    _ => None,
                })
                .collect();

            // Store pending tool uses for permission batching (only for top-level, not subagents)
            if parent_tool_use_id.is_none() {
                PENDING_TOOL_USES.with(|pending| {
                    let tool_uses: Vec<PendingToolUse> = message
                        .content
                        .iter()
                        .filter_map(|block| match block {
                            ContentBlock::ToolUse { id, name, input } => Some(PendingToolUse {
                                tool_use_id: id.clone(),
                                tool_name: name.clone(),
                                tool_input: input.clone(),
                            }),
                            _ => None,
                        })
                        .collect();
                    // Append to existing pending tools instead of replacing
                    pending.borrow_mut().extend(tool_uses);
                });
            }

            // Track message cost for display
            let mut message_cost: Option<MessageCost> = None;

            // Only update stats if we have usage information
            if let Some(usage) = &message.usage {
                // Get model from message, or fall back to last known model from stats
                let model = message.model.clone().or_else(|| {
                    let stats_guard = stats.read();
                    stats_guard.model.clone()
                }).unwrap_or_else(|| {
                    tracing::warn!("No model info available for cost calculation, using default");
                    "claude-sonnet-4-5-20250929".to_string()
                });

                // Calculate cost for historical tracking (including cache tokens)
                let cost_usd = calculate_cost(
                    usage.input_tokens,
                    usage.output_tokens,
                    &model,
                    usage.cache_creation_input_tokens,
                    usage.cache_read_input_tokens,
                );

                tracing::info!("Assistant message tokens - input: {}, output: {}, cache_creation: {:?}, cache_read: {:?}, cost: ${:.4}",
                    usage.input_tokens,
                    usage.output_tokens,
                    usage.cache_creation_input_tokens,
                    usage.cache_read_input_tokens,
                    cost_usd
                );

                // Store cost for later use in output events
                message_cost = Some(MessageCost {
                    input_tokens: usage.input_tokens,
                    output_tokens: usage.output_tokens,
                    cost_usd,
                });

                // Batch all stats updates in a single write lock
                {
                    let mut stats_guard = stats.write();
                    stats_guard.increment_messages();
                    stats_guard.add_usage(
                        usage.input_tokens,
                        usage.output_tokens,
                        &model,
                        usage.cache_creation_input_tokens,
                        usage.cache_read_input_tokens,
                    );
                    stats_guard.get_session_duration();

                    // Attribute tokens to tools if any tools were used in this message
                    if !tools_in_message.is_empty() {
                        let per_tool_input = usage.input_tokens / tools_in_message.len() as u64;
                        let per_tool_output = usage.output_tokens / tools_in_message.len() as u64;
                        for tool in &tools_in_message {
                            stats_guard.add_tool_tokens(tool, per_tool_input, per_tool_output);
                        }
                    }
                }

                // Record to historical cost tracking
                let app_clone = app.clone();
                let input = usage.input_tokens;
                let output = usage.output_tokens;
                tauri::async_runtime::spawn(async move {
                    record_cost(&app_clone, input, output, cost_usd).await;
                });

                // Don't emit here - we'll emit on Result message instead
                // This reduces the frequency of updates
            } else {
                // Just increment message count if no usage info
                stats.write().increment_messages();
            }

            for block in &message.content {
                match block {
                    ContentBlock::ToolUse { id, name, input } => {
                        tool_name = Some(name.clone());
                        state = get_tool_state(name);

                        // Batch tool tracking updates
                        {
                            let mut stats_guard = stats.write();
                            stats_guard.increment_tool_usage(name);

                            // Track file operations
                            match name.as_str() {
                                "Edit" => stats_guard.increment_files_edited(),
                                "Write" => stats_guard.increment_files_created(),
                                _ => {}
                            }
                        }

                        // Emit agent-start event for Task tool invocations
                        // Support both "Task" and "Task(agent_type)" syntax (CLI v2.1.33+)
                        if name == "Task" || name.starts_with("Task(") {
                            let description = input
                                .get("description")
                                .and_then(|v| v.as_str())
                                .unwrap_or("Subagent")
                                .to_string();
                            let subagent_type = input
                                .get("subagent_type")
                                .and_then(|v| v.as_str())
                                .unwrap_or("unknown")
                                .to_string();
                            let now = SystemTime::now()
                                .duration_since(UNIX_EPOCH)
                                .unwrap_or_default()
                                .as_millis() as u64;

                            tracing::debug!(
                                "Emitting agent-start: id={}, desc={}, type={}, parent={:?}",
                                id, description, subagent_type, parent_tool_use_id
                            );

                            let _ = app.emit(
                                "claude:agent-start",
                                AgentStartEvent {
                                    tool_use_id: id.clone(),
                                    agent_id: None, // Will be updated when SubagentStart hook is received
                                    description,
                                    subagent_type,
                                    started_at: now,
                                    conversation_id: conversation_id.clone(),
                                    parent_tool_use_id: parent_tool_use_id.clone(),
                                },
                            );
                        }

                        // Emit todo-update event for TodoWrite tool invocations
                        if name == "TodoWrite" {
                            if let Some(todos_value) = input.get("todos") {
                                if let Some(todos_array) = todos_value.as_array() {
                                    let todos: Vec<TodoItem> = todos_array
                                        .iter()
                                        .filter_map(|todo| {
                                            serde_json::from_value(todo.clone()).ok()
                                        })
                                        .collect();

                                    tracing::debug!(
                                        "Emitting todo-update: {} todos, parent={:?}",
                                        todos.len(),
                                        parent_tool_use_id
                                    );

                                    let _ = app.emit(
                                        "claude:todo-update",
                                        TodoUpdateEvent {
                                            todos,
                                            conversation_id: conversation_id.clone(),
                                        },
                                    );
                                }
                            }
                        }

                        let desc = format_tool_description(name, input);
                        let _ = app.emit(
                            "claude:output",
                            OutputEvent {
                                line_type: "tool".to_string(),
                                content: desc,
                                tool_name: Some(name.clone()),
                                conversation_id: conversation_id.clone(),
                                cost: None, // Tool use doesn't have separate cost
                                parent_tool_use_id: parent_tool_use_id.clone(),
                            },
                        );
                    }
                    ContentBlock::Text { text } => {
                        // Count code blocks in the text
                        let code_blocks = text.matches("```").count() / 2;
                        for _ in 0..code_blocks {
                            stats.write().increment_code_blocks();
                        }

                        let _ = app.emit(
                            "claude:output",
                            OutputEvent {
                                line_type: "assistant".to_string(),
                                content: text.clone(),
                                tool_name: None,
                                conversation_id: conversation_id.clone(),
                                cost: message_cost.clone(), // Include cost with assistant text
                                parent_tool_use_id: parent_tool_use_id.clone(),
                            },
                        );
                    }
                    ContentBlock::Thinking { thinking } => {
                        state = CharacterState::Thinking;
                        let _ = app.emit(
                            "claude:output",
                            OutputEvent {
                                line_type: "thinking".to_string(),
                                content: thinking.clone(),
                                tool_name: None,
                                conversation_id: conversation_id.clone(),
                                cost: None,
                                parent_tool_use_id: parent_tool_use_id.clone(),
                            },
                        );
                    }
                    ContentBlock::ToolResult {
                        tool_use_id,
                        is_error,
                        ..
                    } => {
                        // Emit agent-end for all tool results
                        // The frontend will ignore IDs that don't match known agents
                        let now = SystemTime::now()
                            .duration_since(UNIX_EPOCH)
                            .unwrap_or_default()
                            .as_millis() as u64;

                        let _ = app.emit(
                            "claude:agent-end",
                            AgentEndEvent {
                                tool_use_id: tool_use_id.clone(),
                                ended_at: now,
                                is_error: is_error.unwrap_or(false),
                                conversation_id: conversation_id.clone(),
                                duration_ms: None,
                                num_turns: None,
                            },
                        );
                    }
                }
            }

            emit_state_change(app, state, tool_name, conversation_id.clone());
        }

        ClaudeMessage::StreamEvent { event } => {
            if event.event_type == "content_block_start" {
                if let Some(block) = &event.content_block {
                    // Track tool calls for cost estimation
                    if block.block_type == "tool_use" {
                        if let Some(name) = &block.name {
                            let mut stats_guard = stats.write();
                            stats_guard.current_request_tools.push(name.clone());
                        }
                    }

                    let state = match block.block_type.as_str() {
                        "thinking" => CharacterState::Thinking,
                        "text" => CharacterState::Typing,
                        "tool_use" => {
                            if let Some(name) = &block.name {
                                get_tool_state(name)
                            } else {
                                CharacterState::Typing
                            }
                        }
                        _ => CharacterState::Typing,
                    };
                    emit_state_change(app, state, block.name.clone(), conversation_id.clone());
                }
            } else if event.event_type == "content_block_delta" {
                if let Some(delta) = &event.delta {
                    if let Some(text) = &delta.text {
                        // Track output characters for cost estimation
                        {
                            let mut stats_guard = stats.write();
                            stats_guard.current_request_output_chars += text.len() as u64;
                        }
                        let _ = app.emit("claude:stream", text.clone());
                    } else if let Some(thinking) = &delta.thinking {
                        // Track thinking characters for cost estimation
                        let mut stats_guard = stats.write();
                        stats_guard.current_request_thinking_chars += thinking.len() as u64;
                    }
                }
            }
        }

        ClaudeMessage::Result {
            subtype,
            result,
            permission_denials,
            usage,
            duration_ms,
            num_turns,
        } => {
            tracing::info!(
                "Received Result message: subtype={}, has_denials={}, denial_count={:?}",
                subtype,
                permission_denials.is_some(),
                permission_denials.as_ref().map(|d| d.len())
            );

            let state = if subtype == "success" {
                CharacterState::Success
            } else {
                CharacterState::Error
            };

            // Capture pending tool uses before clearing them
            // We'll use these for permission batching if there are denials
            let captured_pending_tools = PENDING_TOOL_USES.with(|pending| {
                let tools = pending.borrow().clone();
                // Clear immediately so they don't accumulate across requests
                pending.borrow_mut().clear();
                tools
            });

            tracing::debug!(
                "Captured {} pending tool use(s): {:?}",
                captured_pending_tools.len(),
                captured_pending_tools.iter().map(|t| &t.tool_name).collect::<Vec<_>>()
            );

            // Log turn metrics if available
            if let Some(duration) = duration_ms {
                tracing::info!("Turn completed in {}ms", duration);
            }
            if let Some(turns) = num_turns {
                tracing::info!("Turn count: {}", turns);
            }

            // Track token usage from Result messages if available
            // This captures tokens from tool outputs and other operations
            if let Some(usage_info) = usage {
                // We need the model info to calculate cost properly
                // For now, use the last known model from stats
                let model = {
                    let stats_guard = stats.read();
                    stats_guard.model.clone().unwrap_or_else(|| "claude-opus-4-20250514".to_string())
                };

                // Calculate cost for historical tracking (including cache tokens)
                let cost_usd = calculate_cost(
                    usage_info.input_tokens,
                    usage_info.output_tokens,
                    &model,
                    usage_info.cache_creation_input_tokens,
                    usage_info.cache_read_input_tokens,
                );

                let mut stats_guard = stats.write();
                stats_guard.add_usage(
                    usage_info.input_tokens,
                    usage_info.output_tokens,
                    &model,
                    usage_info.cache_creation_input_tokens,
                    usage_info.cache_read_input_tokens,
                );
                tracing::info!("Result message tokens - input: {}, output: {}, cache_creation: {:?}, cache_read: {:?}",
                    usage_info.input_tokens,
                    usage_info.output_tokens,
                    usage_info.cache_creation_input_tokens,
                    usage_info.cache_read_input_tokens
                );

                // Record to historical cost tracking
                let app_clone = app.clone();
                let input = usage_info.input_tokens;
                let output = usage_info.output_tokens;
                tauri::async_runtime::spawn(async move {
                    record_cost(&app_clone, input, output, cost_usd).await;
                });
            }

            // Clear tracking fields since request completed successfully
            {
                let mut stats_guard = stats.write();
                stats_guard.current_request_input = None;
                stats_guard.current_request_output_chars = 0;
                stats_guard.current_request_thinking_chars = 0;
                stats_guard.current_request_tools.clear();
            }

            // Always emit updated stats on result message (less frequent)
            // This includes the latest session duration
            let newly_unlocked = {
                let mut stats_guard = stats.write();
                stats_guard.get_session_duration();
                tracing::info!("Checking achievements after result message...");
                let unlocked = stats_guard.check_achievements();
                tracing::info!("Newly unlocked achievements: {:?}", unlocked);
                unlocked
            };

            // Emit achievement events for any newly unlocked achievements
            for achievement_id in &newly_unlocked {
                let info = get_achievement_info(achievement_id);
                let _ = app.emit(
                    "achievement:unlocked",
                    AchievementUnlockedEvent { achievement: info },
                );
            }

            // Save achievements after unlocking new ones
            if !newly_unlocked.is_empty() {
                tracing::info!("Saving newly unlocked achievements: {:?}", newly_unlocked);
                let app_handle = app.clone();
                let achievements_progress = stats.read().achievements.clone();

                // Use Tauri's async runtime instead of tokio::spawn
                tauri::async_runtime::spawn(async move {
                    tracing::info!("Spawned save task for achievements");
                    if let Err(e) =
                        crate::achievements::save_achievements(&app_handle, &achievements_progress)
                            .await
                    {
                        tracing::error!("Failed to save achievements: {}", e);
                    } else {
                        tracing::info!("Achievement save task completed successfully");
                    }
                });
            }

            let current_stats = stats.read().clone();
            let stats_event = StatsUpdateEvent {
                stats: current_stats.clone(),
            };
            let _ = app.emit("claude:stats", stats_event);

            // Save stats periodically (every 10 messages to avoid excessive disk writes)
            if current_stats.session_messages_exchanged.is_multiple_of(10)
                && current_stats.session_messages_exchanged > 0
            {
                let app_handle = app.clone();
                tauri::async_runtime::spawn(async move {
                    tracing::info!("Periodic stats save (every 10 messages)...");
                    if let Err(e) = crate::stats::save_stats(&app_handle, &current_stats).await {
                        tracing::error!("Failed to save stats: {}", e);
                    }
                });
            }

            // Only emit error results - success content is already sent via Assistant message
            if subtype != "success" {
                if let Some(text) = result {
                    let _ = app.emit(
                        "claude:output",
                        OutputEvent {
                            line_type: "error".to_string(),
                            content: text.clone(),
                            tool_name: None,
                            conversation_id: conversation_id.clone(),
                            cost: None,
                            parent_tool_use_id: None,
                        },
                    );
                }
            }

            // Check for permission denials and emit prompts for each
            if let Some(denials) = permission_denials {
                // Only process if there are actually denials
                if !denials.is_empty() {
                    let mut regular_permission_requests = Vec::new();

                    // Get denied tool IDs for later comparison
                    let denied_tool_ids: Vec<String> = denials.iter()
                        .map(|d| d.tool_use_id.clone())
                        .collect();

                    // Helper function to check if a tool is a system tool that should never require permission
                    let is_system_tool = |tool_name: &str| -> bool {
                        matches!(tool_name, "ExitPlanMode" | "EnterPlanMode")
                    };

                    for denial in denials {
                    // Skip system tools that should never require permission
                    if is_system_tool(&denial.tool_name) {
                        tracing::debug!(
                            "Skipping system tool: {} (id: {})",
                            denial.tool_name,
                            denial.tool_use_id
                        );
                        continue;
                    }
                    tracing::debug!(
                        "Processing permission denial for: {} (id: {})",
                        denial.tool_name,
                        denial.tool_use_id
                    );

                    // Special handling for AskUserQuestion tool
                    if denial.tool_name == "AskUserQuestion" {
                        if let Some(questions) = denial
                            .tool_input
                            .get("questions")
                            .and_then(|q| q.as_array())
                        {
                            // For now, handle the first question (most common case)
                            if let Some(first_question) = questions.first() {
                                let question_text = first_question
                                    .get("question")
                                    .and_then(|q| q.as_str())
                                    .unwrap_or("Claude has a question for you")
                                    .to_string();

                                let header = first_question
                                    .get("header")
                                    .and_then(|h| h.as_str())
                                    .map(|s| s.to_string());

                                let multi_select = first_question
                                    .get("multiSelect")
                                    .and_then(|m| m.as_bool())
                                    .unwrap_or(false);

                                let options: Vec<QuestionOption> = first_question
                                    .get("options")
                                    .and_then(|opts| opts.as_array())
                                    .map(|opts| {
                                        opts.iter()
                                            .filter_map(|opt| {
                                                let label =
                                                    opt.get("label").and_then(|l| l.as_str())?;
                                                let description = opt
                                                    .get("description")
                                                    .and_then(|d| d.as_str())
                                                    .map(|s| s.to_string());
                                                Some(QuestionOption {
                                                    label: label.to_string(),
                                                    description,
                                                })
                                            })
                                            .collect()
                                    })
                                    .unwrap_or_default();

                                let _ = app.emit(
                                    "claude:question",
                                    UserQuestionEvent {
                                        id: denial.tool_use_id.clone(),
                                        question: question_text,
                                        header,
                                        options,
                                        multi_select,
                                        conversation_id: conversation_id.clone(),
                                    },
                                );
                            }
                        }
                    } else {
                        let description =
                            format_tool_description(&denial.tool_name, &denial.tool_input);
                        regular_permission_requests.push(PermissionPromptEventItem {
                            id: denial.tool_use_id.clone(),
                            tool_name: denial.tool_name.clone(),
                            tool_input: denial.tool_input.clone(),
                            description,
                        });
                    }
                }

                // Check for sibling tools that may have been cancelled
                // Add them to the permission batch so they can be approved together
                for tool_use in captured_pending_tools.iter() {
                    // Skip system tools that should never require permission
                    if is_system_tool(&tool_use.tool_name) {
                        continue;
                    }
                    // Only add tools that weren't explicitly denied (these are likely cancelled siblings)
                    if !denied_tool_ids.contains(&tool_use.tool_use_id) {
                        let description = format_tool_description(&tool_use.tool_name, &tool_use.tool_input);
                        regular_permission_requests.push(PermissionPromptEventItem {
                            id: tool_use.tool_use_id.clone(),
                            tool_name: tool_use.tool_name.clone(),
                            tool_input: tool_use.tool_input.clone(),
                            description,
                        });
                    }
                }

                // Emit all regular permission requests as a single batched event
                if !regular_permission_requests.is_empty() {
                    tracing::info!(
                        "Emitting permission event for {} tool(s) in conversation {:?}",
                        regular_permission_requests.len(),
                        conversation_id
                    );
                    for req in &regular_permission_requests {
                        tracing::debug!(
                            "Permission requested: {} (id: {})",
                            req.tool_name,
                            req.id
                        );
                    }
                    let _ = app.emit(
                        "claude:permission",
                        PermissionPromptEvent {
                            permissions: regular_permission_requests,
                            conversation_id: conversation_id.clone(),
                        },
                    );
                    emit_state_change(
                        app,
                        CharacterState::Permission,
                        None,
                        conversation_id.clone(),
                    );
                    return Ok(());
                }

                    // Show permission state if there were any question denials
                    if !denials.is_empty() {
                        emit_state_change(
                            app,
                            CharacterState::Permission,
                            None,
                            conversation_id.clone(),
                        );
                        return Ok(());
                    }
                } // end of else block for non-empty denials
            } // end of if let Some(denials)

            emit_state_change(app, state, None, conversation_id.clone());
        }

        ClaudeMessage::User { message } => {
            // Increment message count for user messages
            stats.write().increment_messages();

            // Process content blocks for tool results (e.g., background Task agent completions)
            for block in &message.content {
                if let ContentBlock::ToolResult {
                    tool_use_id,
                    is_error,
                    ..
                } = block
                {
                    let now = SystemTime::now()
                        .duration_since(UNIX_EPOCH)
                        .unwrap_or_default()
                        .as_millis() as u64;

                    let _ = app.emit(
                        "claude:agent-end",
                        AgentEndEvent {
                            tool_use_id: tool_use_id.clone(),
                            ended_at: now,
                            is_error: is_error.unwrap_or(false),
                            conversation_id: conversation_id.clone(),
                            duration_ms: None,
                            num_turns: None,
                        },
                    );
                }
            }

            // Extract text content from the message
            let message_text = message
                .content
                .iter()
                .filter_map(|block| match block {
                    crate::types::ContentBlock::Text { text } => Some(text.clone()),
                    _ => None,
                })
                .collect::<Vec<String>>()
                .join(" ");

            // Check achievements after user message
            let newly_unlocked = {
                let mut stats_guard = stats.write();
                tracing::info!("User sent message, checking achievements...");

                // Check message-based achievements
                let mut unlocked = crate::achievements::check_message_achievements(
                    &message_text,
                    &mut stats_guard.achievements,
                );

                // Check stats-based achievements
                let stats_unlocked = stats_guard.check_achievements();
                unlocked.extend(stats_unlocked);

                unlocked
            };

            // Emit achievement events for any newly unlocked achievements
            for achievement_id in &newly_unlocked {
                tracing::info!("User message unlocked achievement: {:?}", achievement_id);
                let info = get_achievement_info(achievement_id);
                let _ = app.emit(
                    "achievement:unlocked",
                    AchievementUnlockedEvent { achievement: info },
                );
            }

            // Save achievements after unlocking new ones
            if !newly_unlocked.is_empty() {
                tracing::info!("Saving newly unlocked achievements from user message");
                let app_handle = app.clone();
                let achievements_progress = stats.read().achievements.clone();
                tauri::async_runtime::spawn(async move {
                    if let Err(e) =
                        crate::achievements::save_achievements(&app_handle, &achievements_progress)
                            .await
                    {
                        tracing::error!("Failed to save achievements: {}", e);
                    } else {
                        tracing::info!("Achievements saved after user message");
                    }
                });
            }

            emit_state_change(app, CharacterState::Thinking, None, conversation_id.clone());
        }
    }

    Ok(())
}

fn get_tool_state(tool_name: &str) -> CharacterState {
    if SEARCH_TOOLS.contains(&tool_name) {
        CharacterState::Searching
    } else if CODING_TOOLS.contains(&tool_name) {
        CharacterState::Coding
    } else if tool_name.starts_with("mcp__") {
        CharacterState::Mcp
    } else if tool_name == "Task" || tool_name.starts_with("Task(") {
        CharacterState::Thinking
    } else {
        CharacterState::Typing
    }
}

fn format_tool_description(name: &str, input: &serde_json::Value) -> String {
    // Helper function to check if a path is a memory file
    fn is_memory_path(path: &str) -> bool {
        path.contains("/.claude/") && (path.contains("/memory/") || path.ends_with("/MEMORY.md"))
    }

    match name {
        "Read" => {
            if let Some(path) = input.get("file_path").and_then(|v| v.as_str()) {
                if is_memory_path(path) {
                    // Extract just the filename for cleaner display
                    let filename = path.split('/').next_back().unwrap_or(path);
                    format!("📝 Reading memory: {}", filename)
                } else {
                    format!("Reading file: {}", path)
                }
            } else {
                "Reading file...".to_string()
            }
        }
        "Glob" => {
            if let Some(pattern) = input.get("pattern").and_then(|v| v.as_str()) {
                format!("Searching for files: {}", pattern)
            } else {
                "Searching for files...".to_string()
            }
        }
        "Grep" => {
            if let Some(pattern) = input.get("pattern").and_then(|v| v.as_str()) {
                format!("Searching for: {}", pattern)
            } else {
                "Searching in files...".to_string()
            }
        }
        "Edit" => {
            if let Some(path) = input.get("file_path").and_then(|v| v.as_str()) {
                if is_memory_path(path) {
                    let filename = path.split('/').next_back().unwrap_or(path);
                    format!("💾 Updating memory: {}", filename)
                } else {
                    format!("Editing: {}", path)
                }
            } else {
                "Editing file...".to_string()
            }
        }
        "Write" => {
            if let Some(path) = input.get("file_path").and_then(|v| v.as_str()) {
                if is_memory_path(path) {
                    let filename = path.split('/').next_back().unwrap_or(path);
                    format!("💾 Writing memory: {}", filename)
                } else {
                    format!("Editing: {}", path)
                }
            } else {
                "Editing file...".to_string()
            }
        }
        "Bash" => {
            if let Some(cmd) = input.get("command").and_then(|v| v.as_str()) {
                let truncated = if cmd.len() > 50 {
                    format!("{}...", &cmd[..50])
                } else {
                    cmd.to_string()
                };
                format!("Running: {}", truncated)
            } else {
                "Running command...".to_string()
            }
        }
        _ => format!("Using tool: {}", name),
    }
}

fn emit_state_change(
    app: &AppHandle,
    state: CharacterState,
    tool_name: Option<String>,
    conversation_id: Option<String>,
) {
    let _ = app.emit(
        "claude:state",
        StateChangeEvent {
            state,
            tool_name,
            conversation_id,
        },
    );
}

fn emit_connection_status(
    app: &AppHandle,
    status: ConnectionStatus,
    conversation_id: Option<String>,
) {
    let _ = app.emit(
        "claude:connection",
        ConnectionEvent {
            status,
            conversation_id,
        },
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_tool_state_search_tools() {
        assert!(matches!(get_tool_state("Read"), CharacterState::Searching));
        assert!(matches!(get_tool_state("Glob"), CharacterState::Searching));
        assert!(matches!(get_tool_state("Grep"), CharacterState::Searching));
        assert!(matches!(
            get_tool_state("WebSearch"),
            CharacterState::Searching
        ));
        assert!(matches!(
            get_tool_state("WebFetch"),
            CharacterState::Searching
        ));
    }

    #[test]
    fn test_get_tool_state_coding_tools() {
        assert!(matches!(get_tool_state("Edit"), CharacterState::Coding));
        assert!(matches!(get_tool_state("Write"), CharacterState::Coding));
        assert!(matches!(
            get_tool_state("NotebookEdit"),
            CharacterState::Coding
        ));
    }

    #[test]
    fn test_get_tool_state_mcp_tools() {
        assert!(matches!(
            get_tool_state("mcp__github__create_issue"),
            CharacterState::Mcp
        ));
        assert!(matches!(
            get_tool_state("mcp__notion__search"),
            CharacterState::Mcp
        ));
    }

    #[test]
    fn test_get_tool_state_task() {
        assert!(matches!(get_tool_state("Task"), CharacterState::Thinking));
        // Test CLI v2.1.33+ Task(agent_type) syntax
        assert!(matches!(
            get_tool_state("Task(Explore)"),
            CharacterState::Thinking
        ));
        assert!(matches!(
            get_tool_state("Task(Plan)"),
            CharacterState::Thinking
        ));
        assert!(matches!(
            get_tool_state("Task(general-purpose)"),
            CharacterState::Thinking
        ));
    }

    #[test]
    fn test_get_tool_state_unknown() {
        assert!(matches!(
            get_tool_state("SomeUnknownTool"),
            CharacterState::Typing
        ));
        assert!(matches!(get_tool_state("Bash"), CharacterState::Typing));
    }

    #[test]
    fn test_format_tool_description_read() {
        let input = serde_json::json!({"file_path": "/home/test/file.txt"});
        let desc = format_tool_description("Read", &input);
        assert_eq!(desc, "Reading file: /home/test/file.txt");
    }

    #[test]
    fn test_format_tool_description_read_no_path() {
        let input = serde_json::json!({});
        let desc = format_tool_description("Read", &input);
        assert_eq!(desc, "Reading file...");
    }

    #[test]
    fn test_format_tool_description_glob() {
        let input = serde_json::json!({"pattern": "**/*.rs"});
        let desc = format_tool_description("Glob", &input);
        assert_eq!(desc, "Searching for files: **/*.rs");
    }

    #[test]
    fn test_format_tool_description_grep() {
        let input = serde_json::json!({"pattern": "TODO"});
        let desc = format_tool_description("Grep", &input);
        assert_eq!(desc, "Searching for: TODO");
    }

    #[test]
    fn test_format_tool_description_edit() {
        let input = serde_json::json!({"file_path": "/home/test/main.rs"});
        let desc = format_tool_description("Edit", &input);
        assert_eq!(desc, "Editing: /home/test/main.rs");
    }

    #[test]
    fn test_format_tool_description_write() {
        let input = serde_json::json!({"file_path": "/home/test/new.txt"});
        let desc = format_tool_description("Write", &input);
        assert_eq!(desc, "Editing: /home/test/new.txt");
    }

    #[test]
    fn test_format_tool_description_bash_short() {
        let input = serde_json::json!({"command": "ls -la"});
        let desc = format_tool_description("Bash", &input);
        assert_eq!(desc, "Running: ls -la");
    }

    #[test]
    fn test_format_tool_description_bash_long() {
        let long_cmd = "a".repeat(100);
        let input = serde_json::json!({"command": long_cmd});
        let desc = format_tool_description("Bash", &input);
        assert!(desc.starts_with("Running: "));
        assert!(desc.ends_with("..."));
        assert!(desc.len() < 70);
    }

    #[test]
    fn test_format_tool_description_unknown() {
        let input = serde_json::json!({"some": "data"});
        let desc = format_tool_description("CustomTool", &input);
        assert_eq!(desc, "Using tool: CustomTool");
    }

    #[test]
    fn test_format_tool_description_memory_read() {
        let input =
            serde_json::json!({"file_path": "/home/user/.claude/projects/test/memory/MEMORY.md"});
        let desc = format_tool_description("Read", &input);
        assert_eq!(desc, "📝 Reading memory: MEMORY.md");
    }

    #[test]
    fn test_format_tool_description_memory_write() {
        let input = serde_json::json!(
            {"file_path": "/home/user/.claude/projects/test/memory/notes.md"}
        );
        let desc = format_tool_description("Write", &input);
        assert_eq!(desc, "💾 Writing memory: notes.md");
    }

    #[test]
    fn test_format_tool_description_memory_edit() {
        let input = serde_json::json!(
            {"file_path": "/home/user/.claude/projects/test/memory/patterns.md"}
        );
        let desc = format_tool_description("Edit", &input);
        assert_eq!(desc, "💾 Updating memory: patterns.md");
    }

    #[test]
    fn test_format_tool_description_non_memory_read() {
        let input = serde_json::json!({"file_path": "/home/user/code/test.txt"});
        let desc = format_tool_description("Read", &input);
        assert_eq!(desc, "Reading file: /home/user/code/test.txt");
    }

    #[test]
    fn test_wsl_bridge_new() {
        let bridge = WslBridge::new();
        assert!(!bridge.is_running());
        assert_eq!(bridge.get_working_directory(), "");
    }

    #[test]
    fn test_wsl_bridge_default() {
        let bridge = WslBridge::default();
        assert!(!bridge.is_running());
    }

    #[test]
    fn test_create_shared_bridge_manager() {
        use crate::bridge_manager::create_shared_bridge_manager;
        let shared = create_shared_bridge_manager();
        let manager = shared.lock();
        assert!(manager.get_active_conversations().is_empty());
    }
}
