//! Interactive TUI for tmux session management using Ratatui.
//!
//! Provides a terminal UI for selecting and managing tmux sessions with
//! keyboard navigation, session creation, renaming, and deletion.

use crate::error::TmuxFzfError;
use crate::status::{self, PaneStatus, PaneMonitor};
use crate::tmux::{TmuxClient, TmuxPane, TmuxSession, TmuxWindow};
use crossterm::{
    event::{self, Event, KeyCode, KeyEventKind, KeyModifiers},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{
    backend::CrosstermBackend,
    layout::{Alignment, Constraint, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph},
    Frame, Terminal,
};
use std::io::{self, Stdout};
use std::time::{Duration, Instant};

const TICK_RATE: Duration = Duration::from_millis(250);
const REFRESH_RATE: Duration = Duration::from_secs(2);
const PREVIEW_REFRESH: Duration = Duration::from_secs(1);
const MIN_PREVIEW_WIDTH: u16 = 80;
const STATUS_REFRESH: Duration = Duration::from_secs(2);

/// Parse ANSI escape sequences and convert to ratatui Spans with styles.
fn parse_ansi_line(line: &str) -> Line<'static> {
    let mut spans: Vec<Span<'static>> = Vec::new();
    let mut current_style = Style::default();
    let mut current_text = String::new();
    let mut chars = line.chars().peekable();

    while let Some(c) = chars.next() {
        if c == '\x1b' {
            // Flush current text
            if !current_text.is_empty() {
                spans.push(Span::styled(std::mem::take(&mut current_text), current_style));
            }

            // Parse escape sequence
            if chars.peek() == Some(&'[') {
                chars.next(); // consume '['
                let mut seq = String::new();
                while let Some(&ch) = chars.peek() {
                    if ch.is_ascii_alphabetic() {
                        chars.next();
                        if ch == 'm' {
                            current_style = parse_sgr_sequence(&seq, current_style);
                        }
                        break;
                    }
                    seq.push(chars.next().unwrap());
                }
            }
        } else {
            current_text.push(c);
        }
    }

    // Flush remaining text
    if !current_text.is_empty() {
        spans.push(Span::styled(current_text, current_style));
    }

    if spans.is_empty() {
        Line::from("")
    } else {
        Line::from(spans)
    }
}

/// Parse SGR (Select Graphic Rendition) parameters and update style.
fn parse_sgr_sequence(seq: &str, mut style: Style) -> Style {
    if seq.is_empty() {
        return Style::default();
    }

    let params: Vec<u8> = seq
        .split(';')
        .filter_map(|s| s.parse().ok())
        .collect();

    let mut i = 0;
    while i < params.len() {
        match params[i] {
            0 => style = Style::default(),
            1 => style = style.add_modifier(Modifier::BOLD),
            2 => style = style.add_modifier(Modifier::DIM),
            3 => style = style.add_modifier(Modifier::ITALIC),
            4 => style = style.add_modifier(Modifier::UNDERLINED),
            7 => style = style.add_modifier(Modifier::REVERSED),
            22 => style = style.remove_modifier(Modifier::BOLD | Modifier::DIM),
            23 => style = style.remove_modifier(Modifier::ITALIC),
            24 => style = style.remove_modifier(Modifier::UNDERLINED),
            27 => style = style.remove_modifier(Modifier::REVERSED),
            // Standard foreground colors
            30 => style = style.fg(Color::Black),
            31 => style = style.fg(Color::Red),
            32 => style = style.fg(Color::Green),
            33 => style = style.fg(Color::Yellow),
            34 => style = style.fg(Color::Blue),
            35 => style = style.fg(Color::Magenta),
            36 => style = style.fg(Color::Cyan),
            37 => style = style.fg(Color::Gray),
            39 => style = style.fg(Color::Reset),
            // Bright foreground colors
            90 => style = style.fg(Color::DarkGray),
            91 => style = style.fg(Color::LightRed),
            92 => style = style.fg(Color::LightGreen),
            93 => style = style.fg(Color::LightYellow),
            94 => style = style.fg(Color::LightBlue),
            95 => style = style.fg(Color::LightMagenta),
            96 => style = style.fg(Color::LightCyan),
            97 => style = style.fg(Color::White),
            // Standard background colors
            40 => style = style.bg(Color::Black),
            41 => style = style.bg(Color::Red),
            42 => style = style.bg(Color::Green),
            43 => style = style.bg(Color::Yellow),
            44 => style = style.bg(Color::Blue),
            45 => style = style.bg(Color::Magenta),
            46 => style = style.bg(Color::Cyan),
            47 => style = style.bg(Color::Gray),
            49 => style = style.bg(Color::Reset),
            // Bright background colors
            100 => style = style.bg(Color::DarkGray),
            101 => style = style.bg(Color::LightRed),
            102 => style = style.bg(Color::LightGreen),
            103 => style = style.bg(Color::LightYellow),
            104 => style = style.bg(Color::LightBlue),
            105 => style = style.bg(Color::LightMagenta),
            106 => style = style.bg(Color::LightCyan),
            107 => style = style.bg(Color::White),
            // 256-color and RGB
            38 => {
                if i + 2 < params.len() && params[i + 1] == 5 {
                    style = style.fg(Color::Indexed(params[i + 2]));
                    i += 2;
                } else if i + 4 < params.len() && params[i + 1] == 2 {
                    style = style.fg(Color::Rgb(params[i + 2], params[i + 3], params[i + 4]));
                    i += 4;
                }
            }
            48 => {
                if i + 2 < params.len() && params[i + 1] == 5 {
                    style = style.bg(Color::Indexed(params[i + 2]));
                    i += 2;
                } else if i + 4 < params.len() && params[i + 1] == 2 {
                    style = style.bg(Color::Rgb(params[i + 2], params[i + 3], params[i + 4]));
                    i += 4;
                }
            }
            _ => {}
        }
        i += 1;
    }
    style
}

/// Tree node representing an item in the session/window/pane hierarchy.
#[derive(Debug, Clone, PartialEq)]
enum TreeNode {
    Session(usize),
    Window { session_idx: usize, window_idx: usize },
    Pane { session_idx: usize, window_idx: usize, pane_idx: usize },
}

/// State for a window including its panes (lazy loaded).
#[derive(Debug, Clone)]
struct WindowState {
    window: TmuxWindow,
    expanded: bool,
    panes: Option<Vec<TmuxPane>>,
}

/// State for a session including its windows (lazy loaded).
#[derive(Debug, Clone)]
struct SessionState {
    session: TmuxSession,
    expanded: bool,
    windows: Option<Vec<WindowState>>,
}

/// Target for attachment - session, window, or pane.
#[derive(Debug, Clone)]
enum AttachTarget {
    Session(String),
    Window { session: String, window_index: u32 },
    Pane { session: String, window_index: u32, pane_index: u32 },
}

/// Target for kill operations.
#[derive(Debug, Clone)]
enum KillTarget {
    Session { name: String },
    Window { session: String, window_index: u32, window_name: String },
    Pane { session: String, window_index: u32, pane_index: u32, command: String },
}

impl KillTarget {
    fn display_name(&self) -> String {
        match self {
            KillTarget::Session { name } => format!("session '{}'", name),
            KillTarget::Window { window_name, .. } => format!("window '{}'", window_name),
            KillTarget::Pane { command, .. } => format!("pane '{}'", command),
        }
    }
}

/// Target for rename operations.
#[derive(Debug, Clone)]
enum RenameTarget {
    Session { name: String },
    Window { session: String, window_index: u32, window_name: String },
}

impl RenameTarget {
    fn display_name(&self) -> String {
        match self {
            RenameTarget::Session { name } => format!("session '{}'", name),
            RenameTarget::Window { window_name, .. } => format!("window '{}'", window_name),
        }
    }
}

#[derive(Clone)]
enum Mode {
    Normal,
    CreateSession,
    Rename(RenameTarget),
    ConfirmKill(KillTarget),
}

pub struct App {
    tmux_client: TmuxClient,
    session_states: Vec<SessionState>,
    visible_nodes: Vec<TreeNode>,
    selected_index: usize,
    list_state: ListState,
    mode: Mode,
    input_buffer: String,
    should_quit: bool,
    message: Option<(String, Instant)>,
    preview_content: Option<String>,
    preview_target: Option<String>,
    preview_last_update: Option<Instant>,
    preview_scroll: u16,
    preview_enabled: bool,
    status_monitor: PaneMonitor,
    status_last_update: Option<Instant>,
    status_enabled: bool,
}

impl App {
    pub fn new() -> Self {
        let mut list_state = ListState::default();
        list_state.select(Some(0));

        Self {
            tmux_client: TmuxClient::new(),
            session_states: Vec::new(),
            visible_nodes: Vec::new(),
            selected_index: 0,
            list_state,
            mode: Mode::Normal,
            input_buffer: String::new(),
            should_quit: false,
            message: None,
            preview_content: None,
            preview_target: None,
            preview_last_update: None,
            preview_scroll: 0,
            preview_enabled: true,
            status_monitor: PaneMonitor::new(),
            status_last_update: None,
            status_enabled: true,
        }
    }

    pub fn run(&mut self) -> Result<(), TmuxFzfError> {
        let mut terminal = setup_terminal()?;

        self.refresh_sessions();

        let result = self.main_loop(&mut terminal);

        restore_terminal(&mut terminal)?;

        result
    }

    fn main_loop(&mut self, terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), TmuxFzfError> {
        let mut last_refresh = Instant::now();

        loop {
            self.update_preview();
            self.update_statuses();
            terminal.draw(|f| self.render(f))?;

            if event::poll(TICK_RATE)? {
                if let Event::Key(key) = event::read()? {
                    if key.kind != KeyEventKind::Press {
                        continue;
                    }

                    match self.handle_key(key.code, key.modifiers, terminal)? {
                        KeyAction::Continue => {}
                        KeyAction::Quit => break,
                        KeyAction::Attach(target) => {
                            restore_terminal(terminal)?;
                            let attach_result = match &target {
                                AttachTarget::Session(session) => {
                                    self.tmux_client.attach_session(session)
                                }
                                AttachTarget::Window { session, window_index } => {
                                    self.tmux_client.attach_window(session, *window_index)
                                }
                                AttachTarget::Pane { session, window_index, pane_index } => {
                                    self.tmux_client.attach_pane(session, *window_index, *pane_index)
                                }
                            };
                            *terminal = setup_terminal()?;
                            self.tmux_client.clear_cache();
                            self.refresh_sessions();

                            if let Err(e) = attach_result {
                                self.set_message(format!("Error: {}", e));
                            }
                        }
                    }
                }
            }

            if last_refresh.elapsed() >= REFRESH_RATE {
                self.refresh_sessions();
                last_refresh = Instant::now();
            }

            if self.should_quit {
                break;
            }

            self.clear_expired_message();
        }

        Ok(())
    }

    fn refresh_sessions(&mut self) {
        match self.tmux_client.list_sessions() {
            Ok(sessions) => {
                // Preserve expanded state when refreshing
                let old_states: std::collections::HashMap<String, SessionState> = self
                    .session_states
                    .drain(..)
                    .map(|s| (s.session.name.to_string(), s))
                    .collect();

                self.session_states = sessions
                    .into_iter()
                    .map(|session| {
                        let (windows, expanded) = if let Some(old_state) = old_states.get(session.name.as_ref()) {
                            let windows = if old_state.expanded {
                                // Re-fetch windows for expanded sessions
                                self.fetch_windows_state(&session.name, old_state.windows.as_ref())
                            } else {
                                None
                            };
                            (windows, old_state.expanded)
                        } else {
                            (None, false)
                        };
                        SessionState {
                            session,
                            expanded,
                            windows,
                        }
                    })
                    .collect();

                self.rebuild_visible_nodes();

                // Adjust selection if needed
                if self.selected_index >= self.visible_nodes.len() && !self.visible_nodes.is_empty() {
                    self.selected_index = self.visible_nodes.len() - 1;
                }
                self.sync_list_state();
            }
            Err(TmuxFzfError::NoSessions) => {
                self.session_states.clear();
                self.visible_nodes.clear();
                self.selected_index = 0;
                self.list_state.select(None);
            }
            Err(e) => {
                self.set_message(format!("Error refreshing: {}", e));
            }
        }
    }

    fn fetch_windows_state(
        &self,
        session_name: &str,
        old_windows: Option<&Vec<WindowState>>,
    ) -> Option<Vec<WindowState>> {
        let windows = self.tmux_client.list_windows(session_name).ok()?;

        // Build map of old window states by index to preserve expanded state
        let old_window_map: std::collections::HashMap<u32, &WindowState> = old_windows
            .map(|ws| ws.iter().map(|w| (w.window.index, w)).collect())
            .unwrap_or_default();

        Some(
            windows
                .into_iter()
                .map(|window| {
                    let old_state = old_window_map.get(&window.index);
                    let expanded = old_state.map_or(false, |s| s.expanded);
                    let panes = if expanded {
                        // Re-fetch panes for expanded windows
                        self.tmux_client
                            .list_panes(session_name, window.index)
                            .ok()
                    } else {
                        None
                    };
                    WindowState {
                        window,
                        expanded,
                        panes,
                    }
                })
                .collect(),
        )
    }

    fn rebuild_visible_nodes(&mut self) {
        self.visible_nodes.clear();

        for session_idx in 0..self.session_states.len() {
            self.visible_nodes.push(TreeNode::Session(session_idx));

            let session_state = &self.session_states[session_idx];
            if session_state.expanded {
                if let Some(ref windows) = session_state.windows {
                    let window_count = windows.len();
                    for window_idx in 0..window_count {
                        let window_state = &windows[window_idx];
                        self.visible_nodes.push(TreeNode::Window { session_idx, window_idx });

                        if window_state.expanded {
                            if let Some(ref panes) = window_state.panes {
                                for pane_idx in 0..panes.len() {
                                    self.visible_nodes.push(TreeNode::Pane {
                                        session_idx,
                                        window_idx,
                                        pane_idx,
                                    });
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    fn sync_list_state(&mut self) {
        if self.visible_nodes.is_empty() {
            self.list_state.select(None);
        } else {
            self.list_state.select(Some(self.selected_index));
        }
    }

    fn toggle_expand(&mut self) {
        if self.visible_nodes.is_empty() {
            return;
        }

        let node = self.visible_nodes[self.selected_index].clone();
        match node {
            TreeNode::Session(session_idx) => {
                let session_state = &mut self.session_states[session_idx];
                if session_state.expanded {
                    session_state.expanded = false;
                } else {
                    // Lazy load windows if not yet loaded
                    if session_state.windows.is_none() {
                        match self.tmux_client.list_windows(&session_state.session.name) {
                            Ok(windows) => {
                                session_state.windows = Some(
                                    windows
                                        .into_iter()
                                        .map(|w| WindowState {
                                            window: w,
                                            expanded: false,
                                            panes: None,
                                        })
                                        .collect(),
                                );
                            }
                            Err(e) => {
                                self.set_message(format!("Error loading windows: {}", e));
                                return;
                            }
                        }
                    }
                    session_state.expanded = true;
                }
            }
            TreeNode::Window { session_idx, window_idx } => {
                let session_state = &mut self.session_states[session_idx];
                if let Some(ref mut windows) = session_state.windows {
                    let window_state = &mut windows[window_idx];
                    if window_state.expanded {
                        window_state.expanded = false;
                    } else {
                        // Lazy load panes if not yet loaded
                        if window_state.panes.is_none() {
                            match self.tmux_client.list_panes(
                                &session_state.session.name,
                                window_state.window.index,
                            ) {
                                Ok(panes) => {
                                    window_state.panes = Some(panes);
                                }
                                Err(e) => {
                                    self.set_message(format!("Error loading panes: {}", e));
                                    return;
                                }
                            }
                        }
                        window_state.expanded = true;
                    }
                }
            }
            TreeNode::Pane { .. } => {
                // Panes cannot be expanded
            }
        }
        self.rebuild_visible_nodes();
        self.sync_list_state();
    }

    fn collapse_or_go_parent(&mut self) {
        if self.visible_nodes.is_empty() {
            return;
        }

        let node = self.visible_nodes[self.selected_index].clone();
        match node {
            TreeNode::Session(session_idx) => {
                if self.session_states[session_idx].expanded {
                    self.session_states[session_idx].expanded = false;
                    self.rebuild_visible_nodes();
                    self.sync_list_state();
                }
            }
            TreeNode::Window { session_idx, window_idx } => {
                let session_state = &mut self.session_states[session_idx];
                if let Some(ref mut windows) = session_state.windows {
                    if windows[window_idx].expanded {
                        // Collapse this window
                        windows[window_idx].expanded = false;
                        self.rebuild_visible_nodes();
                        self.sync_list_state();
                    } else {
                        // Go to parent session
                        if let Some(pos) = self.visible_nodes.iter().position(|n| *n == TreeNode::Session(session_idx)) {
                            self.selected_index = pos;
                            self.sync_list_state();
                        }
                    }
                }
            }
            TreeNode::Pane { session_idx, window_idx, .. } => {
                // Go to parent window
                if let Some(pos) = self.visible_nodes.iter().position(|n| *n == TreeNode::Window { session_idx, window_idx }) {
                    self.selected_index = pos;
                    self.sync_list_state();
                }
            }
        }
    }

    fn expand_or_enter_child(&mut self) {
        if self.visible_nodes.is_empty() {
            return;
        }

        let node = self.visible_nodes[self.selected_index].clone();
        match node {
            TreeNode::Session(session_idx) => {
                let needs_expand = !self.session_states[session_idx].expanded;
                if needs_expand {
                    self.toggle_expand();
                }
                // Move to first child if expanded and has children
                let has_windows = self.session_states[session_idx]
                    .windows
                    .as_ref()
                    .map_or(false, |w| !w.is_empty());
                if self.session_states[session_idx].expanded && has_windows {
                    if let Some(pos) = self.visible_nodes.iter().position(|n| {
                        *n == TreeNode::Window { session_idx, window_idx: 0 }
                    }) {
                        self.selected_index = pos;
                        self.sync_list_state();
                    }
                }
            }
            TreeNode::Window { session_idx, window_idx } => {
                let needs_expand = self.session_states[session_idx]
                    .windows
                    .as_ref()
                    .map_or(false, |w| !w[window_idx].expanded);
                if needs_expand {
                    self.toggle_expand();
                }
                // Move to first pane if expanded and has panes
                let has_panes = self.session_states[session_idx]
                    .windows
                    .as_ref()
                    .and_then(|w| w[window_idx].panes.as_ref())
                    .map_or(false, |p| !p.is_empty());
                let is_expanded = self.session_states[session_idx]
                    .windows
                    .as_ref()
                    .map_or(false, |w| w[window_idx].expanded);
                if is_expanded && has_panes {
                    if let Some(pos) = self.visible_nodes.iter().position(|n| {
                        *n == TreeNode::Pane { session_idx, window_idx, pane_idx: 0 }
                    }) {
                        self.selected_index = pos;
                        self.sync_list_state();
                    }
                }
            }
            TreeNode::Pane { .. } => {
                // Panes have no children, do nothing
            }
        }
    }

    fn get_selected_attach_target(&self) -> Option<AttachTarget> {
        if self.visible_nodes.is_empty() {
            return None;
        }

        let node = &self.visible_nodes[self.selected_index];
        match node {
            TreeNode::Session(session_idx) => {
                let session = &self.session_states[*session_idx].session;
                Some(AttachTarget::Session(session.name.to_string()))
            }
            TreeNode::Window { session_idx, window_idx } => {
                let session = &self.session_states[*session_idx].session;
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window = &windows[*window_idx].window;
                    Some(AttachTarget::Window {
                        session: session.name.to_string(),
                        window_index: window.index,
                    })
                } else {
                    None
                }
            }
            TreeNode::Pane { session_idx, window_idx, pane_idx } => {
                let session = &self.session_states[*session_idx].session;
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window = &windows[*window_idx].window;
                    if let Some(ref panes) = windows[*window_idx].panes {
                        let pane = &panes[*pane_idx];
                        Some(AttachTarget::Pane {
                            session: session.name.to_string(),
                            window_index: window.index,
                            pane_index: pane.index,
                        })
                    } else {
                        None
                    }
                } else {
                    None
                }
            }
        }
    }

    fn get_selected_kill_target(&self) -> Option<KillTarget> {
        if self.visible_nodes.is_empty() {
            return None;
        }

        let node = &self.visible_nodes[self.selected_index];
        match node {
            TreeNode::Session(session_idx) => {
                let session = &self.session_states[*session_idx].session;
                Some(KillTarget::Session { name: session.name.to_string() })
            }
            TreeNode::Window { session_idx, window_idx } => {
                let session = &self.session_states[*session_idx].session;
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window = &windows[*window_idx].window;
                    Some(KillTarget::Window {
                        session: session.name.to_string(),
                        window_index: window.index,
                        window_name: window.name.to_string(),
                    })
                } else {
                    None
                }
            }
            TreeNode::Pane { session_idx, window_idx, pane_idx } => {
                let session = &self.session_states[*session_idx].session;
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window = &windows[*window_idx].window;
                    if let Some(ref panes) = windows[*window_idx].panes {
                        let pane = &panes[*pane_idx];
                        Some(KillTarget::Pane {
                            session: session.name.to_string(),
                            window_index: window.index,
                            pane_index: pane.index,
                            command: pane.command.to_string(),
                        })
                    } else {
                        None
                    }
                } else {
                    None
                }
            }
        }
    }

    fn get_selected_rename_target(&self) -> Option<RenameTarget> {
        if self.visible_nodes.is_empty() {
            return None;
        }

        let node = &self.visible_nodes[self.selected_index];
        match node {
            TreeNode::Session(session_idx) => {
                let session = &self.session_states[*session_idx].session;
                Some(RenameTarget::Session { name: session.name.to_string() })
            }
            TreeNode::Window { session_idx, window_idx } => {
                let session = &self.session_states[*session_idx].session;
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window = &windows[*window_idx].window;
                    Some(RenameTarget::Window {
                        session: session.name.to_string(),
                        window_index: window.index,
                        window_name: window.name.to_string(),
                    })
                } else {
                    None
                }
            }
            TreeNode::Pane { .. } => {
                // Panes cannot be renamed
                None
            }
        }
    }

    fn get_preview_pane_target(&self) -> Option<String> {
        if self.visible_nodes.is_empty() {
            return None;
        }

        let node = &self.visible_nodes[self.selected_index];
        match node {
            TreeNode::Session(session_idx) => {
                let session = &self.session_states[*session_idx].session;
                self.tmux_client.get_active_pane_target(&session.name).ok()
            }
            TreeNode::Window { session_idx, window_idx } => {
                let session = &self.session_states[*session_idx].session;
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window = &windows[*window_idx].window;
                    self.tmux_client
                        .get_window_active_pane_target(&session.name, window.index)
                        .ok()
                } else {
                    None
                }
            }
            TreeNode::Pane { session_idx, window_idx, pane_idx } => {
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    if let Some(ref panes) = windows[*window_idx].panes {
                        Some(panes[*pane_idx].pane_id.to_string())
                    } else {
                        None
                    }
                } else {
                    None
                }
            }
        }
    }

    fn calculate_tree_width(&self) -> usize {
        if self.session_states.is_empty() {
            return 40; // Default width for empty state message
        }

        self.visible_nodes
            .iter()
            .map(|node| self.get_tree_node_width(node))
            .max()
            .unwrap_or(20)
    }

    fn get_tree_node_width(&self, node: &TreeNode) -> usize {
        let indicator_width = if self.status_enabled { 2 } else { 0 };
        match node {
            TreeNode::Session(session_idx) => {
                let session_state = &self.session_states[*session_idx];
                let session = &session_state.session;
                // "● ▼ {name} : {N} windows (attached)"
                let status_len = if session.is_attached() { 11 } else { 0 };
                indicator_width + 2 + session.name.len() + 3 + format!("{}", session.windows).len() + 8 + status_len
            }
            TreeNode::Window { session_idx, window_idx } => {
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window_state = &windows[*window_idx];
                    let window = &window_state.window;
                    // "    ├── ● ▼ {name} : {N} panes *"
                    let active_len = if window.is_active { 2 } else { 0 };
                    4 + 4 + indicator_width + 2 + window.name.len() + 3 + format!("{}", window.pane_count).len() + 6 + active_len
                } else {
                    0
                }
            }
            TreeNode::Pane { session_idx, window_idx, pane_idx } => {
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    if let Some(ref panes) = windows[*window_idx].panes {
                        let pane = &panes[*pane_idx];
                        // "    │       └── ● {command} *"
                        let active_len = if pane.is_active { 2 } else { 0 };
                        4 + 4 + 4 + 4 + indicator_width + pane.command.len() + active_len
                    } else {
                        0
                    }
                } else {
                    0
                }
            }
        }
    }

    fn get_preview_title(&self) -> String {
        if self.visible_nodes.is_empty() {
            return " Preview ".to_string();
        }

        let node = &self.visible_nodes[self.selected_index];
        match node {
            TreeNode::Session(session_idx) => {
                let session = &self.session_states[*session_idx].session;
                format!(" Preview: {} ", session.name)
            }
            TreeNode::Window { session_idx, window_idx } => {
                let session = &self.session_states[*session_idx].session;
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window = &windows[*window_idx].window;
                    format!(" Preview: {}:{} ", session.name, window.name)
                } else {
                    " Preview ".to_string()
                }
            }
            TreeNode::Pane { session_idx, window_idx, pane_idx } => {
                let session = &self.session_states[*session_idx].session;
                if let Some(ref windows) = self.session_states[*session_idx].windows {
                    let window = &windows[*window_idx].window;
                    if let Some(ref panes) = windows[*window_idx].panes {
                        let pane = &panes[*pane_idx];
                        format!(" Preview: {}:{}:{} ", session.name, window.name, pane.command)
                    } else {
                        " Preview ".to_string()
                    }
                } else {
                    " Preview ".to_string()
                }
            }
        }
    }

    fn set_message(&mut self, msg: String) {
        self.message = Some((msg, Instant::now()));
    }

    fn clear_expired_message(&mut self) {
        if let Some((_, time)) = &self.message {
            if time.elapsed() > Duration::from_secs(3) {
                self.message = None;
            }
        }
    }

    fn update_preview(&mut self) {
        if !self.preview_enabled {
            return;
        }

        let new_target = self.get_preview_pane_target();

        let target_changed = self.preview_target != new_target;
        let needs_refresh = self.preview_last_update
            .map_or(true, |t| t.elapsed() >= PREVIEW_REFRESH);

        if target_changed {
            self.preview_scroll = 0;
            self.preview_target = new_target.clone();
        }

        if !target_changed && !needs_refresh {
            return;
        }

        if let Some(ref target) = new_target {
            match self.tmux_client.capture_pane(target) {
                Ok(content) => {
                    self.preview_content = Some(content);
                    self.preview_last_update = Some(Instant::now());
                }
                Err(_) => {
                    self.preview_content = None;
                }
            }
        } else {
            self.preview_content = None;
        }
    }

    fn scroll_preview_down(&mut self) {
        if let Some(ref content) = self.preview_content {
            let line_count = content.lines().count() as u16;
            if self.preview_scroll < line_count.saturating_sub(1) {
                self.preview_scroll += 1;
            }
        }
    }

    fn scroll_preview_up(&mut self) {
        if self.preview_scroll > 0 {
            self.preview_scroll -= 1;
        }
    }

    fn toggle_preview(&mut self) {
        self.preview_enabled = !self.preview_enabled;
        if self.preview_enabled {
            self.preview_last_update = None;
        }
    }

    fn update_statuses(&mut self) {
        if !self.status_enabled {
            return;
        }
        if self.status_last_update.map_or(false, |t| t.elapsed() < STATUS_REFRESH) {
            return;
        }

        let pane_infos = match self.tmux_client.list_all_panes_info() {
            Ok(infos) => infos,
            Err(_) => return,
        };

        self.status_monitor.begin_update();

        for info in &pane_infos {
            let content = self.tmux_client.capture_pane_plain(&info.pane_id).ok();
            let status = status::classify(&info.command, content.as_deref(), &info.pane_id);
            self.status_monitor.set(&info.pane_id, &info.session_name, info.window_index, status);
        }

        self.status_last_update = Some(Instant::now());
    }

    fn get_pane_status(&self, pane_id: &str) -> PaneStatus {
        self.status_monitor.get_pane(pane_id)
    }

    fn get_session_status(&self, session_idx: usize) -> PaneStatus {
        let name = &self.session_states[session_idx].session.name;
        self.status_monitor.get_session(name)
    }

    fn get_window_status(&self, session_idx: usize, window_idx: usize) -> PaneStatus {
        let state = &self.session_states[session_idx];
        if let Some(ref windows) = state.windows {
            let ws = &windows[window_idx];
            self.status_monitor.get_window(&state.session.name, ws.window.index)
        } else {
            PaneStatus::Unknown
        }
    }

    fn handle_key(
        &mut self,
        code: KeyCode,
        modifiers: KeyModifiers,
        _terminal: &mut Terminal<CrosstermBackend<Stdout>>,
    ) -> Result<KeyAction, TmuxFzfError> {
        match &self.mode.clone() {
            Mode::Normal => self.handle_normal_key(code, modifiers),
            Mode::CreateSession => self.handle_input_key(code, InputMode::Create),
            Mode::Rename(target) => self.handle_input_key(code, InputMode::Rename(target.clone())),
            Mode::ConfirmKill(target) => self.handle_confirm_key(code, target.clone()),
        }
    }

    fn handle_normal_key(&mut self, code: KeyCode, modifiers: KeyModifiers) -> Result<KeyAction, TmuxFzfError> {
        match code {
            KeyCode::Char('q') | KeyCode::Esc => Ok(KeyAction::Quit),
            KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => Ok(KeyAction::Quit),
            KeyCode::Up => {
                self.select_previous();
                Ok(KeyAction::Continue)
            }
            KeyCode::Down | KeyCode::Char('j') => {
                self.select_next();
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('k') if modifiers.contains(KeyModifiers::CONTROL) => {
                if let Some(target) = self.get_selected_kill_target() {
                    self.mode = Mode::ConfirmKill(target);
                }
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('k') => {
                self.select_previous();
                Ok(KeyAction::Continue)
            }
            KeyCode::Enter => {
                if let Some(target) = self.get_selected_attach_target() {
                    Ok(KeyAction::Attach(target))
                } else {
                    Ok(KeyAction::Continue)
                }
            }
            KeyCode::Tab | KeyCode::Char(' ') => {
                self.toggle_expand();
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('h') | KeyCode::Left => {
                self.collapse_or_go_parent();
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('l') | KeyCode::Right => {
                self.expand_or_enter_child();
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('n') => {
                self.mode = Mode::CreateSession;
                self.input_buffer.clear();
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('r') => {
                if let Some(target) = self.get_selected_rename_target() {
                    self.mode = Mode::Rename(target);
                    self.input_buffer.clear();
                } else {
                    self.set_message("Panes cannot be renamed".to_string());
                }
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('d') | KeyCode::Char('x') => {
                if let Some(target) = self.get_selected_kill_target() {
                    self.mode = Mode::ConfirmKill(target);
                }
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('p') => {
                self.toggle_preview();
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('s') => {
                self.status_enabled = !self.status_enabled;
                if !self.status_enabled {
                    self.status_monitor.clear();
                } else {
                    self.status_last_update = None;
                }
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('J') => {
                self.scroll_preview_down();
                Ok(KeyAction::Continue)
            }
            KeyCode::Char('K') => {
                self.scroll_preview_up();
                Ok(KeyAction::Continue)
            }
            _ => Ok(KeyAction::Continue),
        }
    }

    fn handle_input_key(&mut self, code: KeyCode, input_mode: InputMode) -> Result<KeyAction, TmuxFzfError> {
        match code {
            KeyCode::Esc => {
                self.mode = Mode::Normal;
                self.input_buffer.clear();
            }
            KeyCode::Enter => {
                let input = self.input_buffer.trim().to_string();
                self.input_buffer.clear();
                self.mode = Mode::Normal;

                match input_mode {
                    InputMode::Create => {
                        let name = if input.is_empty() { None } else { Some(input.as_str()) };
                        match self.tmux_client.new_session(name) {
                            Ok(session_name) => {
                                self.set_message(format!("Created session '{}'", session_name));
                                self.refresh_sessions();
                            }
                            Err(e) => {
                                self.set_message(format!("Error: {}", e));
                            }
                        }
                    }
                    InputMode::Rename(target) => {
                        if !input.is_empty() {
                            let result = match &target {
                                RenameTarget::Session { name } => {
                                    self.tmux_client.rename_session(name, &input)
                                }
                                RenameTarget::Window { session, window_index, .. } => {
                                    self.tmux_client.rename_window(session, *window_index, &input)
                                }
                            };
                            match result {
                                Ok(()) => {
                                    self.set_message(format!("Renamed {} to '{}'", target.display_name(), input));
                                    self.refresh_sessions();
                                }
                                Err(e) => {
                                    self.set_message(format!("Error: {}", e));
                                }
                            }
                        }
                    }
                }
            }
            KeyCode::Backspace => {
                self.input_buffer.pop();
            }
            KeyCode::Char(c) => {
                self.input_buffer.push(c);
            }
            _ => {}
        }
        Ok(KeyAction::Continue)
    }

    fn handle_confirm_key(&mut self, code: KeyCode, target: KillTarget) -> Result<KeyAction, TmuxFzfError> {
        match code {
            KeyCode::Char('y') | KeyCode::Char('Y') => {
                let result = match &target {
                    KillTarget::Session { name } => {
                        self.tmux_client.kill_session(name)
                    }
                    KillTarget::Window { session, window_index, .. } => {
                        self.tmux_client.kill_window(session, *window_index)
                    }
                    KillTarget::Pane { session, window_index, pane_index, .. } => {
                        self.tmux_client.kill_pane(session, *window_index, *pane_index)
                    }
                };
                match result {
                    Ok(()) => {
                        self.set_message(format!("Killed {}", target.display_name()));
                        self.refresh_sessions();
                    }
                    Err(e) => {
                        self.set_message(format!("Error: {}", e));
                    }
                }
                self.mode = Mode::Normal;
            }
            KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
                self.mode = Mode::Normal;
            }
            _ => {}
        }
        Ok(KeyAction::Continue)
    }

    fn select_next(&mut self) {
        if self.visible_nodes.is_empty() {
            return;
        }
        if self.selected_index >= self.visible_nodes.len() - 1 {
            self.selected_index = 0;
        } else {
            self.selected_index += 1;
        }
        self.sync_list_state();
    }

    fn select_previous(&mut self) {
        if self.visible_nodes.is_empty() {
            return;
        }
        if self.selected_index == 0 {
            self.selected_index = self.visible_nodes.len() - 1;
        } else {
            self.selected_index -= 1;
        }
        self.sync_list_state();
    }

    fn render(&mut self, frame: &mut Frame) {
        let area = frame.area();
        let show_preview = self.preview_enabled && area.width >= MIN_PREVIEW_WIDTH;

        let chunks = Layout::vertical([
            Constraint::Length(1),
            Constraint::Min(5),
            Constraint::Length(2),
        ])
        .split(area);

        self.render_title(frame, chunks[0]);

        if show_preview {
            // Calculate the width needed for the tree based on content
            let tree_width = self.calculate_tree_width();
            // Add padding for borders (2), highlight symbol (2), and some margin (2)
            let tree_width_with_padding = (tree_width + 6) as u16;
            // Ensure minimum width and cap at 50% of available space
            let max_tree_width = chunks[1].width / 2;
            let actual_tree_width = tree_width_with_padding.max(20).min(max_tree_width);

            let content_chunks = Layout::horizontal([
                Constraint::Length(actual_tree_width),
                Constraint::Min(MIN_PREVIEW_WIDTH.min(chunks[1].width.saturating_sub(actual_tree_width))),
            ])
            .split(chunks[1]);

            self.render_tree(frame, content_chunks[0]);
            self.render_preview(frame, content_chunks[1]);
        } else {
            self.render_tree(frame, chunks[1]);
        }

        self.render_help(frame, chunks[2]);

        match &self.mode {
            Mode::CreateSession => {
                self.render_input_modal(frame, "New Session", "Enter session name (empty for auto):");
            }
            Mode::Rename(target) => {
                let title = match target {
                    RenameTarget::Session { .. } => "Rename Session",
                    RenameTarget::Window { .. } => "Rename Window",
                };
                self.render_input_modal(frame, title, &format!("New name for {}:", target.display_name()));
            }
            Mode::ConfirmKill(target) => {
                self.render_confirm_modal(frame, &format!("Kill {}?", target.display_name()));
            }
            Mode::Normal => {}
        }
    }

    fn render_title(&self, frame: &mut Frame, area: Rect) {
        let title = if let Some((msg, _)) = &self.message {
            Paragraph::new(msg.as_str())
                .style(Style::default().fg(Color::Yellow))
                .alignment(Alignment::Center)
        } else {
            Paragraph::new("TmuxTango - Dance between your sessions!")
                .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
                .alignment(Alignment::Center)
        };
        frame.render_widget(title, area);
    }

    fn render_tree(&mut self, frame: &mut Frame, area: Rect) {
        if self.session_states.is_empty() {
            let empty = Paragraph::new("No tmux sessions found. Press 'n' to create one.")
                .style(Style::default().fg(Color::Gray))
                .alignment(Alignment::Center)
                .block(Block::default().borders(Borders::ALL).title(" Sessions "));
            frame.render_widget(empty, area);
            return;
        }

        let selected_idx = self.list_state.selected();
        let items: Vec<ListItem> = self
            .visible_nodes
            .iter()
            .enumerate()
            .map(|(i, node)| self.render_tree_node(node, selected_idx == Some(i)))
            .collect();

        let list = List::new(items)
            .block(Block::default().borders(Borders::ALL).title(" Sessions "))
            .highlight_style(
                Style::default()
                    .bg(Color::Gray)
                    .add_modifier(Modifier::BOLD),
            )
            .highlight_symbol("> ");

        frame.render_stateful_widget(list, area, &mut self.list_state);
    }

    fn render_tree_node(&self, node: &TreeNode, selected: bool) -> ListItem<'static> {
        let info_color = if selected { Color::White } else { Color::Gray };
        match node {
            TreeNode::Session(session_idx) => {
                let session_state = &self.session_states[*session_idx];
                let session = &session_state.session;
                let expand_symbol = if session_state.expanded { "▼" } else { "▶" };
                let attached = if session.is_attached() { " (attached)" } else { "" };

                let mut spans = Vec::new();
                if self.status_enabled {
                    spans.push(status::status_indicator(self.get_session_status(*session_idx)));
                    spans.push(Span::raw(" "));
                }
                spans.push(Span::raw(format!("{} ", expand_symbol)));
                spans.push(Span::styled(session.name.to_string(), Style::default().fg(Color::Cyan)));
                spans.push(Span::styled(
                    format!(" : {} windows{}", session.windows, attached),
                    Style::default().fg(info_color),
                ));

                ListItem::new(Line::from(spans))
            }
            TreeNode::Window { session_idx, window_idx } => {
                let session_state = &self.session_states[*session_idx];
                if let Some(ref windows) = session_state.windows {
                    let window_state = &windows[*window_idx];
                    let window = &window_state.window;
                    let is_last_window = *window_idx == windows.len() - 1;
                    let branch = if is_last_window { "└──" } else { "├──" };
                    let expand_symbol = if window_state.expanded { "▼" } else { "▶" };
                    let active_marker = if window.is_active { " *" } else { "" };

                    let mut spans = vec![
                        Span::raw(format!("    {} ", branch)),
                    ];
                    if self.status_enabled {
                        spans.push(status::status_indicator(self.get_window_status(*session_idx, *window_idx)));
                        spans.push(Span::raw(" "));
                    }
                    spans.push(Span::raw(format!("{} ", expand_symbol)));
                    spans.push(Span::styled(window.name.to_string(), Style::default().fg(Color::Yellow)));
                    spans.push(Span::styled(
                        format!(" : {} panes{}", window.pane_count, active_marker),
                        Style::default().fg(info_color),
                    ));

                    ListItem::new(Line::from(spans))
                } else {
                    ListItem::new(Line::from(""))
                }
            }
            TreeNode::Pane { session_idx, window_idx, pane_idx } => {
                let session_state = &self.session_states[*session_idx];
                if let Some(ref windows) = session_state.windows {
                    let window_state = &windows[*window_idx];
                    let is_last_window = *window_idx == windows.len() - 1;
                    let continuation = if is_last_window { "    " } else { "│   " };

                    if let Some(ref panes) = window_state.panes {
                        let pane = &panes[*pane_idx];
                        let is_last_pane = *pane_idx == panes.len() - 1;
                        let branch = if is_last_pane { "└──" } else { "├──" };
                        let active_marker = if pane.is_active { " *" } else { "" };

                        let mut spans = vec![
                            Span::raw(format!("    {}    {} ", continuation, branch)),
                        ];
                        if self.status_enabled {
                            spans.push(status::status_indicator(self.get_pane_status(&pane.pane_id)));
                            spans.push(Span::raw(" "));
                        }
                        spans.push(Span::styled(pane.command.to_string(), Style::default().fg(Color::Green)));
                        spans.push(Span::styled(
                            active_marker.to_string(),
                            Style::default().fg(info_color),
                        ));

                        ListItem::new(Line::from(spans))
                    } else {
                        ListItem::new(Line::from(""))
                    }
                } else {
                    ListItem::new(Line::from(""))
                }
            }
        }
    }

    fn render_preview(&self, frame: &mut Frame, area: Rect) {
        let title = self.get_preview_title();
        let block = Block::default()
            .borders(Borders::ALL)
            .title(title)
            .style(Style::default());

        let inner = block.inner(area);
        frame.render_widget(block, area);

        match &self.preview_content {
            Some(content) => {
                let lines: Vec<Line> = content
                    .lines()
                    .skip(self.preview_scroll as usize)
                    .map(parse_ansi_line)
                    .collect();

                let total_lines = content.lines().count();
                let visible_lines = inner.height as usize;

                let scroll_info = if total_lines > visible_lines {
                    let pos = self.preview_scroll as usize + 1;
                    let max = total_lines.saturating_sub(visible_lines) + 1;
                    format!(" [{}/{}] ", pos, max)
                } else {
                    String::new()
                };

                if !scroll_info.is_empty() {
                    let scroll_indicator = Paragraph::new(scroll_info)
                        .style(Style::default().fg(Color::DarkGray))
                        .alignment(Alignment::Right);
                    let indicator_area = Rect::new(
                        area.x + area.width.saturating_sub(12),
                        area.y,
                        11,
                        1,
                    );
                    frame.render_widget(scroll_indicator, indicator_area);
                }

                let preview_text = Paragraph::new(lines);
                frame.render_widget(preview_text, inner);
            }
            None => {
                let message = if self.visible_nodes.is_empty() {
                    "No session selected"
                } else {
                    "Preview unavailable"
                };
                let empty = Paragraph::new(message)
                    .style(Style::default().fg(Color::DarkGray))
                    .alignment(Alignment::Center);
                frame.render_widget(empty, inner);
            }
        }
    }

    fn render_help(&self, frame: &mut Frame, area: Rect) {
        let (line1, line2) = match &self.mode {
            Mode::Normal => (
                "j/k: Nav | Tab: Expand | h/l: Collapse/Expand | Enter: Attach",
                "n: New | r: Rename | d: Kill | p: Preview | s: Status | J/K: Scroll | q: Quit",
            ),
            Mode::CreateSession | Mode::Rename(_) => (
                "Enter: Confirm | Esc: Cancel",
                "",
            ),
            Mode::ConfirmKill(_) => (
                "y: Confirm | n/Esc: Cancel",
                "",
            ),
        };

        let help = Paragraph::new(vec![
            Line::from(line1),
            Line::from(Span::styled(line2, Style::default().fg(Color::DarkGray))),
        ])
        .style(Style::default().fg(Color::DarkGray))
        .alignment(Alignment::Center);
        frame.render_widget(help, area);
    }

    fn render_input_modal(&self, frame: &mut Frame, title: &str, prompt: &str) {
        let area = centered_rect(50, 7, frame.area());

        frame.render_widget(Clear, area);

        let block = Block::default()
            .borders(Borders::ALL)
            .title(format!(" {} ", title))
            .style(Style::default().bg(Color::Black));

        let inner = block.inner(area);
        frame.render_widget(block, area);

        let chunks = Layout::vertical([Constraint::Length(1), Constraint::Length(1), Constraint::Length(1)])
            .margin(1)
            .split(inner);

        let prompt_widget = Paragraph::new(prompt).style(Style::default().fg(Color::White));
        frame.render_widget(prompt_widget, chunks[0]);

        let input_display = format!("{}_", self.input_buffer);
        let input_widget = Paragraph::new(input_display)
            .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD));
        frame.render_widget(input_widget, chunks[1]);
    }

    fn render_confirm_modal(&self, frame: &mut Frame, message: &str) {
        let area = centered_rect(40, 5, frame.area());

        frame.render_widget(Clear, area);

        let block = Block::default()
            .borders(Borders::ALL)
            .title(" Confirm ")
            .style(Style::default().bg(Color::Black));

        let inner = block.inner(area);
        frame.render_widget(block, area);

        let chunks = Layout::vertical([Constraint::Length(1), Constraint::Length(1)])
            .margin(1)
            .split(inner);

        let msg = Paragraph::new(message)
            .style(Style::default().fg(Color::Yellow))
            .alignment(Alignment::Center);
        frame.render_widget(msg, chunks[0]);

        let hint = Paragraph::new("y/n")
            .style(Style::default().fg(Color::DarkGray))
            .alignment(Alignment::Center);
        frame.render_widget(hint, chunks[1]);
    }
}

enum KeyAction {
    Continue,
    Quit,
    Attach(AttachTarget),
}

enum InputMode {
    Create,
    Rename(RenameTarget),
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>, TmuxFzfError> {
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen)?;
    let backend = CrosstermBackend::new(stdout);
    let terminal = Terminal::new(backend)?;
    Ok(terminal)
}

fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), TmuxFzfError> {
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
    terminal.show_cursor()?;
    Ok(())
}

fn centered_rect(percent_x: u16, height: u16, area: Rect) -> Rect {
    let popup_width = area.width * percent_x / 100;
    let popup_x = (area.width.saturating_sub(popup_width)) / 2;
    let popup_y = (area.height.saturating_sub(height)) / 2;

    Rect::new(
        area.x + popup_x,
        area.y + popup_y,
        popup_width.min(area.width),
        height.min(area.height),
    )
}

/// Interactive session selector - convenience wrapper
pub struct InteractiveSelector {
    app: App,
}

impl InteractiveSelector {
    pub fn new() -> Self {
        Self { app: App::new() }
    }

    pub fn run(&mut self) -> Result<(), TmuxFzfError> {
        self.app.run()
    }
}
