//! Tmux integration and session management.
//!
//! This module provides types and functionality for interacting with tmux,
//! including session parsing, command execution, and caching for performance.

use std::process::{Command, Stdio};
use std::env;
use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::error::TmuxFzfError;

// Constants
const TMUX_ENV_VAR: &str = "TMUX";
const DEFAULT_WINDOWS: &str = "1";
const UNKNOWN_TIME: &str = "unknown";

/// Represents a tmux session with its metadata.
///
/// This struct uses `Arc<str>` for efficient cloning and sharing of session data.
#[derive(Debug, Clone, PartialEq)]
pub struct TmuxSession {
    /// The session name
    pub name: Arc<str>,
    /// Number of windows in the session
    pub windows: Arc<str>,
    /// Creation timestamp information
    #[allow(dead_code)]
    pub created: Arc<str>,
    /// The original output line from tmux
    #[allow(dead_code)]
    pub raw_line: Arc<str>,
}

/// Represents a tmux window within a session.
#[derive(Debug, Clone, PartialEq)]
pub struct TmuxWindow {
    /// The window index (0-based)
    pub index: u32,
    /// The window name
    pub name: Arc<str>,
    /// Number of panes in the window
    pub pane_count: u32,
    /// Whether this window is currently active
    pub is_active: bool,
}

impl TmuxWindow {
    /// Parse a tmux window from formatted output.
    ///
    /// Expected format: `index:name:pane_count:active_flag`
    /// where active_flag is "1" for active, "0" for inactive
    pub fn from_line(line: &str) -> Option<Self> {
        let parts: Vec<&str> = line.splitn(4, ':').collect();
        if parts.len() < 4 {
            return None;
        }

        let index = parts[0].parse().ok()?;
        let name: Arc<str> = parts[1].into();
        let pane_count = parts[2].parse().ok()?;
        let is_active = parts[3] == "1";

        Some(Self {
            index,
            name,
            pane_count,
            is_active,
        })
    }
}

/// Represents a tmux pane within a window.
#[derive(Debug, Clone, PartialEq)]
pub struct TmuxPane {
    /// The pane index (0-based)
    pub index: u32,
    /// The pane ID (e.g., "%0")
    pub pane_id: Arc<str>,
    /// The current command running in the pane
    pub command: Arc<str>,
    /// Whether this pane is currently active
    pub is_active: bool,
    /// The current working directory of the pane
    pub current_path: Arc<str>,
}

impl TmuxPane {
    /// Parse a tmux pane from formatted output.
    ///
    /// Expected format: `index:pane_id:command:active_flag:current_path`
    /// where active_flag is "1" for active, "0" for inactive
    pub fn from_line(line: &str) -> Option<Self> {
        let parts: Vec<&str> = line.splitn(5, ':').collect();
        if parts.len() < 5 {
            return None;
        }

        let index = parts[0].parse().ok()?;
        let pane_id: Arc<str> = parts[1].into();
        let command: Arc<str> = parts[2].into();
        let is_active = parts[3] == "1";
        let current_path: Arc<str> = parts[4].into();

        Some(Self {
            index,
            pane_id,
            command,
            is_active,
            current_path,
        })
    }
}

impl TmuxSession {
    /// Parse a tmux session from a line of `tmux list-sessions` output.
    ///
    /// Expected format: `session-name: N windows (created timestamp)`
    pub fn from_line(line: &str) -> Self {
        // Split only on the first colon to separate name from info
        let mut parts = line.splitn(2, ':');
        let name: Arc<str> = parts.next().unwrap_or("").into();
        
        let info = parts.next().unwrap_or("");
        
        let windows: Arc<str> = if let Some(windows_start) = info.find(" windows") {
            info[..windows_start].trim().into()
        } else if let Some(window_start) = info.find(" window") {
            info[..window_start].trim().into()
        } else if !info.trim().is_empty() {
            info.trim().into()
        } else {
            DEFAULT_WINDOWS.into()
        };
        
        // Find the created timestamp - it starts after " windows" or " window"
        let created: Arc<str> = if let Some(pos) = info.find(" (created ") {
            info[pos..].into()
        } else {
            UNKNOWN_TIME.into()
        };
        
        Self {
            name,
            windows,
            created,
            raw_line: line.into(),
        }
    }

    /// Check if this session is currently attached.
    ///
    /// This is determined by checking if the raw line contains "(attached)"
    pub fn is_attached(&self) -> bool {
        self.raw_line.contains("(attached)")
    }
}

/// Client for interacting with tmux.
///
/// This client provides methods for listing, creating, attaching to, and managing
/// tmux sessions. It includes a cache for improved performance when listing sessions.
pub struct TmuxClient {
    session_cache: Option<(Vec<TmuxSession>, Instant)>,
}

impl TmuxClient {
    const CACHE_DURATION: Duration = Duration::from_millis(500);
    
    pub fn new() -> Self {
        Self {
            session_cache: None,
        }
    }
    
    pub fn list_sessions(&mut self) -> Result<Vec<TmuxSession>, TmuxFzfError> {
        if let Some((ref cached_sessions, cached_time)) = self.session_cache {
            if cached_time.elapsed() < Self::CACHE_DURATION {
                return Ok(cached_sessions.clone());
            }
        }
        
        let output = Command::new("tmux")
            .arg("list-sessions")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux command: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            if stderr.contains("no sessions") || stderr.contains("no server running") {
                return Err(TmuxFzfError::NoSessions);
            }
            return Err(TmuxFzfError::TmuxError(format!("tmux list-sessions failed: {}", stderr)));
        }
        
        let stdout = String::from_utf8_lossy(&output.stdout);
        let sessions: Vec<TmuxSession> = stdout
            .lines()
            .filter(|line| !line.is_empty())
            .map(TmuxSession::from_line)
            .collect();
        
        self.session_cache = Some((sessions.clone(), Instant::now()));
        Ok(sessions)
    }
    
    pub fn attach_session(&self, session_name: &str) -> Result<(), TmuxFzfError> {
        self.attach_target(session_name)
    }

    /// Attach to a specific window within a session.
    ///
    /// The target format is `session:window_index`
    pub fn attach_window(&self, session_name: &str, window_index: u32) -> Result<(), TmuxFzfError> {
        let target = format!("{}:{}", session_name, window_index);
        self.attach_target(&target)
    }

    /// Attach to a specific pane within a window.
    ///
    /// The target format is `session:window_index.pane_index`
    pub fn attach_pane(&self, session_name: &str, window_index: u32, pane_index: u32) -> Result<(), TmuxFzfError> {
        let target = format!("{}:{}.{}", session_name, window_index, pane_index);
        self.attach_target(&target)
    }

    /// Attach to a tmux target (session, window, or pane).
    /// Uses switch-client when inside tmux, attach-session otherwise.
    pub fn attach_target(&self, target: &str) -> Result<(), TmuxFzfError> {
        let (command, action) = if self.is_inside_tmux() {
            ("switch-client", "switch to")
        } else {
            ("attach-session", "attach to")
        };

        let status = Command::new("tmux")
            .arg(command)
            .arg("-t")
            .arg(target)
            .stdin(Stdio::inherit())
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .status()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux {}: {}", command, e)))?;

        if status.success() {
            Ok(())
        } else {
            let error_msg = match status.code() {
                Some(code) => {
                    format!("Failed to {} target '{}' (exit code: {})", action, target, code)
                },
                None => {
                    format!("tmux process was terminated while trying to {} target '{}'", action, target)
                }
            };

            Err(TmuxFzfError::TmuxError(error_msg))
        }
    }
    
    pub fn kill_session(&mut self, session_name: &str) -> Result<(), TmuxFzfError> {
        let status = Command::new("tmux")
            .arg("kill-session")
            .arg("-t")
            .arg(session_name)
            .status()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux kill-session: {}", e)))?;

        if status.success() {
            self.session_cache = None;
            Ok(())
        } else {
            Err(TmuxFzfError::TmuxError(format!(
                "Failed to kill session '{}' (exit code: {})",
                session_name, status.code().unwrap_or(-1)
            )))
        }
    }

    pub fn kill_window(&mut self, session_name: &str, window_index: u32) -> Result<(), TmuxFzfError> {
        let target = format!("{}:{}", session_name, window_index);
        let status = Command::new("tmux")
            .arg("kill-window")
            .arg("-t")
            .arg(&target)
            .status()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux kill-window: {}", e)))?;

        if status.success() {
            self.session_cache = None;
            Ok(())
        } else {
            Err(TmuxFzfError::TmuxError(format!(
                "Failed to kill window '{}' (exit code: {})",
                target, status.code().unwrap_or(-1)
            )))
        }
    }

    pub fn kill_pane(&mut self, session_name: &str, window_index: u32, pane_index: u32) -> Result<(), TmuxFzfError> {
        let target = format!("{}:{}.{}", session_name, window_index, pane_index);
        let status = Command::new("tmux")
            .arg("kill-pane")
            .arg("-t")
            .arg(&target)
            .status()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux kill-pane: {}", e)))?;

        if status.success() {
            self.session_cache = None;
            Ok(())
        } else {
            Err(TmuxFzfError::TmuxError(format!(
                "Failed to kill pane '{}' (exit code: {})",
                target, status.code().unwrap_or(-1)
            )))
        }
    }

    pub fn rename_session(&mut self, old_name: &str, new_name: &str) -> Result<(), TmuxFzfError> {
        if new_name.is_empty() {
            return Err(TmuxFzfError::InvalidSessionName("Name cannot be empty".to_string()));
        }

        if new_name.contains(':') {
            return Err(TmuxFzfError::InvalidSessionName("Name cannot contain ':'".to_string()));
        }

        let status = Command::new("tmux")
            .arg("rename-session")
            .arg("-t")
            .arg(old_name)
            .arg(new_name)
            .status()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux rename-session: {}", e)))?;

        if status.success() {
            self.session_cache = None;
            Ok(())
        } else {
            Err(TmuxFzfError::TmuxError(format!(
                "Failed to rename session '{}' to '{}' (exit code: {})",
                old_name, new_name, status.code().unwrap_or(-1)
            )))
        }
    }

    pub fn rename_window(&mut self, session_name: &str, window_index: u32, new_name: &str) -> Result<(), TmuxFzfError> {
        if new_name.is_empty() {
            return Err(TmuxFzfError::InvalidSessionName("Name cannot be empty".to_string()));
        }

        let target = format!("{}:{}", session_name, window_index);
        let status = Command::new("tmux")
            .arg("rename-window")
            .arg("-t")
            .arg(&target)
            .arg(new_name)
            .status()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux rename-window: {}", e)))?;

        if status.success() {
            self.session_cache = None;
            Ok(())
        } else {
            Err(TmuxFzfError::TmuxError(format!(
                "Failed to rename window '{}' to '{}' (exit code: {})",
                target, new_name, status.code().unwrap_or(-1)
            )))
        }
    }
    
    pub fn new_session(&mut self, session_name: Option<&str>) -> Result<String, TmuxFzfError> {
        self.new_session_with_attach(session_name, true)
    }
    
    fn new_session_with_attach(&mut self, session_name: Option<&str>, should_attach: bool) -> Result<String, TmuxFzfError> {
        if let Some(name) = session_name {
            if name.is_empty() {
                return Err(TmuxFzfError::InvalidSessionName("Session name cannot be empty".to_string()));
            }
            if name.contains(':') {
                return Err(TmuxFzfError::InvalidSessionName("Session name cannot contain ':'".to_string()));
            }
        }
        
        let mut cmd = Command::new("tmux");
        cmd.arg("new-session").arg("-d");
        
        if let Some(name) = session_name {
            cmd.arg("-s").arg(name);
        }
        
        // Set the working directory to current directory to ensure session starts in a good state
        if let Ok(current_dir) = std::env::current_dir() {
            cmd.arg("-c").arg(current_dir);
        }
        
        let output = cmd
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux new-session: {}", e)))?;
        
        if output.status.success() {
            let created_name = if let Some(name) = session_name {
                name.to_string()
            } else {
                self.get_latest_session_name()?
            };
            
            
            if should_attach && !self.is_inside_tmux() {
                self.attach_session(&created_name)?;
            }
            
            self.session_cache = None; // Invalidate cache after successful creation
            Ok(created_name)
        } else {
            let stderr = String::from_utf8_lossy(&output.stderr);
            Err(TmuxFzfError::TmuxError(format!(
                "Failed to create session: {}", stderr
            )))
        }
    }
    
    pub fn is_inside_tmux(&self) -> bool {
        env::var(TMUX_ENV_VAR).is_ok()
    }
    
    fn get_latest_session_name(&mut self) -> Result<String, TmuxFzfError> {
        let sessions = self.list_sessions()?;
        sessions
            .last()
            .map(|s| s.name.as_ref().to_string())
            .ok_or(TmuxFzfError::NoSessions)
    }
    
    #[allow(dead_code)]
    pub fn has_sessions(&mut self) -> bool {
        self.list_sessions().is_ok()
    }

    pub fn clear_cache(&mut self) {
        self.session_cache = None;
    }

    /// Capture the contents of a pane.
    ///
    /// The target can be a pane ID (e.g., "%0") or a session:window.pane target.
    /// Returns the captured pane content as a string.
    pub fn capture_pane(&self, target: &str) -> Result<String, TmuxFzfError> {
        let output = Command::new("tmux")
            .arg("capture-pane")
            .arg("-p")
            .arg("-e")  // Include escape sequences (colors)
            .arg("-t")
            .arg(target)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux capture-pane: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(TmuxFzfError::TmuxError(format!(
                "tmux capture-pane failed for '{}': {}", target, stderr
            )));
        }

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Capture pane contents as plain text (no ANSI escape sequences).
    /// Used for status detection where color information is unnecessary.
    pub fn capture_pane_plain(&self, target: &str) -> Result<String, TmuxFzfError> {
        let output = Command::new("tmux")
            .arg("capture-pane")
            .arg("-p")
            .arg("-t")
            .arg(target)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux capture-pane: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(TmuxFzfError::TmuxError(format!(
                "tmux capture-pane failed for '{}': {}", target, stderr
            )));
        }

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// Get the active pane ID for a session.
    ///
    /// Returns the pane ID (e.g., "%0") of the active pane in the session's active window.
    pub fn get_active_pane_target(&self, session_name: &str) -> Result<String, TmuxFzfError> {
        let output = Command::new("tmux")
            .arg("display-message")
            .arg("-t")
            .arg(session_name)
            .arg("-p")
            .arg("#{pane_id}")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux display-message: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(TmuxFzfError::TmuxError(format!(
                "tmux display-message failed for session '{}': {}", session_name, stderr
            )));
        }

        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    }

    /// Get the active pane ID for a specific window.
    ///
    /// Returns the pane ID (e.g., "%0") of the active pane in the specified window.
    pub fn get_window_active_pane_target(&self, session_name: &str, window_index: u32) -> Result<String, TmuxFzfError> {
        let target = format!("{}:{}", session_name, window_index);
        let output = Command::new("tmux")
            .arg("display-message")
            .arg("-t")
            .arg(&target)
            .arg("-p")
            .arg("#{pane_id}")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux display-message: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(TmuxFzfError::TmuxError(format!(
                "tmux display-message failed for window '{}': {}", target, stderr
            )));
        }

        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    }

    /// List windows for a given session.
    ///
    /// Returns a vector of TmuxWindow structs for the specified session.
    pub fn list_windows(&self, session_name: &str) -> Result<Vec<TmuxWindow>, TmuxFzfError> {
        let output = Command::new("tmux")
            .arg("list-windows")
            .arg("-t")
            .arg(session_name)
            .arg("-F")
            .arg("#{window_index}:#{window_name}:#{window_panes}:#{window_active}")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux list-windows: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(TmuxFzfError::TmuxError(format!(
                "tmux list-windows failed for session '{}': {}", session_name, stderr
            )));
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        let windows: Vec<TmuxWindow> = stdout
            .lines()
            .filter(|line| !line.is_empty())
            .filter_map(TmuxWindow::from_line)
            .collect();

        Ok(windows)
    }

    /// List panes for a given session and window.
    ///
    /// Returns a vector of TmuxPane structs for the specified window.
    pub fn list_panes(&self, session_name: &str, window_index: u32) -> Result<Vec<TmuxPane>, TmuxFzfError> {
        let target = format!("{}:{}", session_name, window_index);
        let output = Command::new("tmux")
            .arg("list-panes")
            .arg("-t")
            .arg(&target)
            .arg("-F")
            .arg("#{pane_index}:#{pane_id}:#{pane_current_command}:#{pane_active}:#{pane_current_path}")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux list-panes: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(TmuxFzfError::TmuxError(format!(
                "tmux list-panes failed for '{}': {}", target, stderr
            )));
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        let panes: Vec<TmuxPane> = stdout
            .lines()
            .filter(|line| !line.is_empty())
            .filter_map(TmuxPane::from_line)
            .collect();

        Ok(panes)
    }

    /// Bulk query returning metadata for all panes across all sessions.
    /// Single tmux call — used for status monitoring without per-pane overhead.
    pub fn list_all_panes_info(&self) -> Result<Vec<PaneInfo>, TmuxFzfError> {
        let output = Command::new("tmux")
            .arg("list-panes")
            .arg("-a")
            .arg("-F")
            .arg("#{pane_id}\t#{session_name}\t#{window_index}\t#{pane_current_command}")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| TmuxFzfError::TmuxError(format!("Failed to execute tmux list-panes -a: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(TmuxFzfError::TmuxError(format!(
                "tmux list-panes -a failed: {}", stderr
            )));
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        let panes = stdout
            .lines()
            .filter(|line| !line.is_empty())
            .filter_map(PaneInfo::from_line)
            .collect();

        Ok(panes)
    }
}

/// Metadata for a pane from bulk query, used for status classification.
#[derive(Debug, Clone)]
pub struct PaneInfo {
    pub pane_id: String,
    pub session_name: String,
    pub window_index: u32,
    pub command: String,
}

impl PaneInfo {
    fn from_line(line: &str) -> Option<Self> {
        let parts: Vec<&str> = line.splitn(4, '\t').collect();
        if parts.len() < 4 {
            return None;
        }
        Some(Self {
            pane_id: parts[0].to_string(),
            session_name: parts[1].to_string(),
            window_index: parts[2].parse().ok()?,
            command: parts[3].to_string(),
        })
    }
}

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

    #[test]
    fn test_tmux_window_from_line_valid() {
        let line = "0:main:2:1";
        let window = TmuxWindow::from_line(line).expect("Should parse valid window line");

        assert_eq!(window.index, 0);
        assert_eq!(window.name.as_ref(), "main");
        assert_eq!(window.pane_count, 2);
        assert!(window.is_active);
    }

    #[test]
    fn test_tmux_window_from_line_inactive() {
        let line = "1:editor:1:0";
        let window = TmuxWindow::from_line(line).expect("Should parse valid window line");

        assert_eq!(window.index, 1);
        assert_eq!(window.name.as_ref(), "editor");
        assert_eq!(window.pane_count, 1);
        assert!(!window.is_active);
    }

    #[test]
    fn test_tmux_window_from_line_special_name() {
        let line = "2:server-main:3:0";
        let window = TmuxWindow::from_line(line).expect("Should parse window with special name");

        assert_eq!(window.index, 2);
        assert_eq!(window.name.as_ref(), "server-main");
        assert_eq!(window.pane_count, 3);
        assert!(!window.is_active);
    }

    #[test]
    fn test_tmux_window_from_line_invalid_too_few_parts() {
        let line = "0:main:2";
        assert!(TmuxWindow::from_line(line).is_none(), "Should fail with too few parts");
    }

    #[test]
    fn test_tmux_window_from_line_invalid_index() {
        let line = "abc:main:2:1";
        assert!(TmuxWindow::from_line(line).is_none(), "Should fail with non-numeric index");
    }

    #[test]
    fn test_tmux_window_from_line_invalid_pane_count() {
        let line = "0:main:abc:1";
        assert!(TmuxWindow::from_line(line).is_none(), "Should fail with non-numeric pane count");
    }

    #[test]
    fn test_tmux_pane_from_line_valid() {
        let line = "0:%0:bash:1:/home/user/projects";
        let pane = TmuxPane::from_line(line).expect("Should parse valid pane line");

        assert_eq!(pane.index, 0);
        assert_eq!(pane.pane_id.as_ref(), "%0");
        assert_eq!(pane.command.as_ref(), "bash");
        assert!(pane.is_active);
        assert_eq!(pane.current_path.as_ref(), "/home/user/projects");
    }

    #[test]
    fn test_tmux_pane_from_line_inactive() {
        let line = "1:%1:nvim:0:/tmp";
        let pane = TmuxPane::from_line(line).expect("Should parse valid pane line");

        assert_eq!(pane.index, 1);
        assert_eq!(pane.pane_id.as_ref(), "%1");
        assert_eq!(pane.command.as_ref(), "nvim");
        assert!(!pane.is_active);
        assert_eq!(pane.current_path.as_ref(), "/tmp");
    }

    #[test]
    fn test_tmux_pane_from_line_with_special_command() {
        let line = "0:%0:node-server:1:/var/www/app";
        let pane = TmuxPane::from_line(line).expect("Should parse pane with special command");

        assert_eq!(pane.index, 0);
        assert_eq!(pane.pane_id.as_ref(), "%0");
        assert_eq!(pane.command.as_ref(), "node-server");
        assert!(pane.is_active);
        assert_eq!(pane.current_path.as_ref(), "/var/www/app");
    }

    #[test]
    fn test_tmux_pane_from_line_with_path_containing_colons() {
        // Paths with colons should still work since we use splitn(5, ':')
        let line = "0:%0:bash:1:/home/user/path:with:colons";
        let pane = TmuxPane::from_line(line).expect("Should parse pane with colons in path");

        assert_eq!(pane.index, 0);
        assert_eq!(pane.pane_id.as_ref(), "%0");
        assert_eq!(pane.command.as_ref(), "bash");
        assert!(pane.is_active);
        assert_eq!(pane.current_path.as_ref(), "/home/user/path:with:colons");
    }

    #[test]
    fn test_tmux_pane_from_line_invalid_too_few_parts() {
        let line = "0:%0:bash:1";
        assert!(TmuxPane::from_line(line).is_none(), "Should fail with too few parts (missing path)");
    }

    #[test]
    fn test_tmux_pane_from_line_invalid_index() {
        let line = "abc:%0:bash:1:/home";
        assert!(TmuxPane::from_line(line).is_none(), "Should fail with non-numeric index");
    }

    #[test]
    fn test_tmux_window_clone() {
        let window = TmuxWindow {
            index: 0,
            name: "test".into(),
            pane_count: 1,
            is_active: true,
        };
        let cloned = window.clone();
        assert_eq!(window, cloned);
    }

    #[test]
    fn test_tmux_pane_clone() {
        let pane = TmuxPane {
            index: 0,
            pane_id: "%0".into(),
            command: "bash".into(),
            is_active: true,
            current_path: "/home/user".into(),
        };
        let cloned = pane.clone();
        assert_eq!(pane, cloned);
    }

    #[test]
    fn test_tmux_session_from_line() {
        let line = "dev: 3 windows (created Mon Jan 27 10:00:00 2025)";
        let session = TmuxSession::from_line(line);

        assert_eq!(session.name.as_ref(), "dev");
        assert_eq!(session.windows.as_ref(), "3");
        assert!(!session.is_attached());
    }

    #[test]
    fn test_tmux_session_attached() {
        let line = "main: 2 windows (created Mon Jan 27 10:00:00 2025) (attached)";
        let session = TmuxSession::from_line(line);

        assert_eq!(session.name.as_ref(), "main");
        assert!(session.is_attached());
    }

    #[test]
    fn test_tmux_session_single_window() {
        let line = "single: 1 window (created Mon Jan 27 10:00:00 2025)";
        let session = TmuxSession::from_line(line);

        assert_eq!(session.name.as_ref(), "single");
        assert_eq!(session.windows.as_ref(), "1");
    }

    #[test]
    fn test_tmux_client_new() {
        let client = TmuxClient::new();
        assert!(client.session_cache.is_none(), "New client should have no cache");
    }

    #[test]
    fn test_tmux_client_clear_cache() {
        let mut client = TmuxClient::new();
        client.clear_cache();
        assert!(client.session_cache.is_none(), "Cache should be cleared");
    }

    #[test]
    fn test_capture_pane_invalid_target_returns_error() {
        let client = TmuxClient::new();
        let result = client.capture_pane("nonexistent-session-12345:0.0");
        // capture_pane with explicit invalid target returns error
        assert!(result.is_err(), "capture_pane with invalid target should return error");
    }

    #[test]
    fn test_get_active_pane_target_returns_result() {
        // display-message may succeed even with invalid session depending on tmux state,
        // so we only verify the method returns a Result without panicking
        let client = TmuxClient::new();
        let _result = client.get_active_pane_target("test-session");
        // Method should not panic regardless of tmux state
    }

    #[test]
    fn test_get_window_active_pane_target_returns_result() {
        // display-message may succeed even with invalid session depending on tmux state,
        // so we only verify the method returns a Result without panicking
        let client = TmuxClient::new();
        let _result = client.get_window_active_pane_target("test-session", 0);
        // Method should not panic regardless of tmux state
    }
}