use crate::{
    confirmation::{ConfirmPrompt, ConfirmStatus},
    filter::{Filter, SearchAction},
    float::{Float, FloatContent},
    floating_text::FloatingText,
    hint::{create_shortcut_list, Shortcut},
    root::check_root_status,
    running_command::RunningCommand,
    shortcuts,
    theme::Theme,
    Args,
};
use linutil_core::{ego_tree::NodeId, Command, Config, ConfigValues, ListNode, TabList};
use ratatui::{
    crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind},
    layout::Flex,
    prelude::*,
    symbols::border,
    widgets::{Block, List, ListState, Paragraph},
};
use std::rc::Rc;

const MIN_WIDTH: u16 = 100;
const MIN_HEIGHT: u16 = 25;
const FLOAT_SIZE: u16 = 80;
const CONFIRM_PROMPT_FLOAT_SIZE: u16 = 40;
const TITLE: &str = concat!(" Linux Toolbox - ", env!("CARGO_PKG_VERSION"), " ");
const ACTIONS_GUIDE: &str = "List of important tasks performed by commands' names:

D  - disk modifications (ex. partitioning) (privileged)
FI - flatpak installation
FM - file modification
I  - installation (privileged)
K  - kernel modifications (privileged)
MP - package manager actions
SI - full system installation
SS - systemd actions (privileged)
RP - package removal

P* - privileged *
";

pub struct AppState {
    /// Areas of tabs
    areas: Option<Areas>,
    /// Selected theme
    theme: Theme,
    /// Currently focused area
    focus: Focus,
    /// List of tabs
    tabs: TabList,
    /// Current tab
    current_tab: ListState,
    longest_tab_display_len: u16,
    /// This stack keeps track of our "current directory". You can think of it as `pwd`. but not
    /// just the current directory, all paths that took us here, so we can "cd .."
    visit_stack: Vec<(NodeId, usize)>,
    /// This is the state associated with the list widget, used to display the selection in the
    /// widget
    selection: ListState,
    filter: Filter,
    multi_select: bool,
    selected_commands: Vec<Rc<ListNode>>,
    drawable: bool,
    #[cfg(feature = "tips")]
    tip: &'static str,
    size_bypass: bool,
    skip_confirmation: bool,
    mouse_enabled: bool,
}

pub enum Focus {
    Search,
    TabList,
    List,
    FloatingWindow(Float<dyn FloatContent>),
    ConfirmationPrompt(Float<ConfirmPrompt>),
}

pub struct ListEntry {
    pub node: Rc<ListNode>,
    pub id: NodeId,
    pub has_children: bool,
}

struct Areas {
    tab_list: Rect,
    list: Rect,
}

enum SelectedItem {
    UpDir,
    Directory,
    Command,
    None,
}

enum ScrollDir {
    Up,
    Down,
}

impl AppState {
    pub fn new(args: Args) -> Self {
        #[cfg(unix)]
        let root_warning = check_root_status(args.bypass_root);
        #[cfg(not(unix))]
        let root_warning = None;

        let tabs = linutil_core::get_tabs(!args.override_validation);
        let root_id = tabs[0].tree.root().id();

        let longest_tab_display_len = tabs
            .iter()
            .map(|tab| tab.name.len() + args.theme.tab_icon().len())
            .max()
            .unwrap_or(22) as u16; // 22 is the length of "Linutil by Chris Titus" title

        let mut state = Self {
            areas: None,
            theme: args.theme,
            focus: Focus::List,
            tabs,
            current_tab: ListState::default().with_selected(Some(0)),
            longest_tab_display_len,
            visit_stack: vec![(root_id, 0usize)],
            selection: ListState::default().with_selected(Some(0)),
            filter: Filter::new(),
            multi_select: false,
            selected_commands: Vec::new(),
            drawable: false,
            #[cfg(feature = "tips")]
            tip: crate::tips::get_random_tip(),
            size_bypass: args.size_bypass,
            skip_confirmation: args.skip_confirmation,
            mouse_enabled: args.mouse,
        };

        #[cfg(unix)]
        if let Some(root_warning) = root_warning {
            state.spawn_float(root_warning, FLOAT_SIZE, FLOAT_SIZE);
        }

        state.update_items();

        if let Some(config_path) = args.config {
            let config = Config::read_config(&config_path, &state.tabs);
            state.apply_config(config);
        }

        state
    }

    fn apply_config(&mut self, config_values: ConfigValues) {
        self.skip_confirmation = self.skip_confirmation || config_values.skip_confirmation;
        self.size_bypass = self.size_bypass || config_values.size_bypass;

        if !config_values.auto_execute_commands.is_empty() {
            self.selected_commands = config_values.auto_execute_commands;
            self.handle_initial_auto_execute();
        }
    }

    fn handle_initial_auto_execute(&mut self) {
        if !self.selected_commands.is_empty() {
            self.spawn_confirmprompt();
        }
    }

    fn spawn_confirmprompt(&mut self) {
        let cmd_names: Vec<_> = self
            .selected_commands
            .iter()
            .map(|node| node.name.as_str())
            .collect();

        let prompt = ConfirmPrompt::new(&cmd_names);
        self.focus = Focus::ConfirmationPrompt(Float::new(
            Box::new(prompt),
            CONFIRM_PROMPT_FLOAT_SIZE,
            CONFIRM_PROMPT_FLOAT_SIZE,
        ));
    }

    fn get_list_item_shortcut(&self) -> Box<[Shortcut]> {
        if self.selected_item_is_dir() {
            shortcuts!(("Go to selected dir", ["l", "Right", "Enter"]))
        } else {
            shortcuts!(
                ("Run selected command", ["l", "Right", "Enter"]),
                ("Enable preview", ["p"]),
                ("Command Description", ["d"])
            )
        }
    }

    pub fn get_keybinds(&self) -> (&str, Box<[Shortcut]>) {
        match self.focus {
            Focus::Search => (
                "Search bar",
                shortcuts!(("Abort search", ["Esc", "CTRL-c"]), ("Search", ["Enter"])),
            ),

            Focus::List => {
                let mut hints = Vec::new();
                hints.push(Shortcut::new("Exit linutil", ["q", "CTRL-c"]));

                if self.at_root() {
                    hints.push(Shortcut::new("Focus tab list", ["h", "Left"]));
                    hints.extend(self.get_list_item_shortcut());
                } else if self.selected_item_is_up_dir() {
                    hints.push(Shortcut::new(
                        "Go to parent directory",
                        ["l", "Right", "Enter", "h", "Left"],
                    ));
                } else {
                    hints.push(Shortcut::new("Go to parent directory", ["h", "Left"]));
                    hints.extend(self.get_list_item_shortcut());
                }

                hints.extend(shortcuts!(
                    ("Select item above", ["k", "Up"]),
                    ("Select item below", ["j", "Down"]),
                    ("Next theme", ["t"]),
                    ("Previous theme", ["T"]),
                    ("Multi-selection mode", ["v"]),
                ));
                if self.multi_select {
                    hints.push(Shortcut::new("Select multiple commands", ["Space"]));
                }
                hints.extend(shortcuts!(
                    ("Next tab", ["Tab"]),
                    ("Previous tab", ["Shift-Tab"]),
                    ("Important actions guide", ["g"])
                ));

                ("Command list", hints.into_boxed_slice())
            }

            Focus::TabList => (
                "Tab list",
                shortcuts!(
                    ("Exit linutil", ["q", "CTRL-c"]),
                    ("Focus action list", ["l", "Right", "Enter"]),
                    ("Select item above", ["k", "Up"]),
                    ("Select item below", ["j", "Down"]),
                    ("Next theme", ["t"]),
                    ("Previous theme", ["T"]),
                    ("Next tab", ["Tab"]),
                    ("Previous tab", ["Shift-Tab"]),
                    ("Important actions guide", ["g"]),
                    ("Multi-selection mode", ["v"]),
                ),
            ),

            Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
            Focus::ConfirmationPrompt(ref prompt) => prompt.get_shortcut_list(),
        }
    }

    fn is_terminal_drawable(&mut self, terminal_size: Rect) -> bool {
        !(self.size_bypass || matches!(self.focus, Focus::FloatingWindow(_)))
            && (terminal_size.height < MIN_HEIGHT || terminal_size.width < MIN_WIDTH)
    }

    pub fn draw(&mut self, frame: &mut Frame) {
        let area = frame.area();
        self.drawable = !self.is_terminal_drawable(area);
        if !self.drawable {
            let warning = Paragraph::new(format!(
                "Terminal size too small:\nWidth = {} Height = {}\n\nMinimum size:\nWidth = {}  Height = {}",
                area.width,
                area.height,
                MIN_WIDTH,
                MIN_HEIGHT,
            ))
                .alignment(Alignment::Center)
                .style(Style::default().fg(self.theme.fail_color()).bold())
                .wrap(ratatui::widgets::Wrap { trim: true });

            let centered_layout = Layout::default()
                .direction(Direction::Vertical)
                .constraints([
                    Constraint::Fill(1),
                    Constraint::Length(5),
                    Constraint::Fill(1),
                ])
                .split(area);

            return frame.render_widget(warning, centered_layout[1]);
        }

        let label_block = Block::bordered().border_set(border::Set {
            top_left: " ",
            top_right: " ",
            bottom_left: " ",
            bottom_right: " ",
            vertical_left: " ",
            vertical_right: " ",
            horizontal_top: "*",
            horizontal_bottom: "*",
        });

        let label = Paragraph::new(Line::from(vec![
            Span::styled("Linutil ", Style::default().bold()),
            Span::styled("by Chris Titus", Style::default().italic()),
        ]))
        .block(label_block)
        .centered();

        let (keybind_scope, shortcuts) = self.get_keybinds();

        let keybinds_block = Block::bordered()
            .title(format!(" {} ", keybind_scope))
            .border_set(border::ROUNDED);

        let keybind_render_width = keybinds_block.inner(area).width;
        let keybinds = create_shortcut_list(shortcuts, keybind_render_width);
        let keybind_len = keybinds.len() as u16;
        let keybind_para = Paragraph::new(Text::from_iter(keybinds)).block(keybinds_block);

        let vertical =
            Layout::vertical([Constraint::Percentage(0), Constraint::Max(keybind_len + 2)])
                .flex(Flex::Legacy)
                .split(area);

        let horizontal = Layout::horizontal([
            Constraint::Min(self.longest_tab_display_len + 5),
            Constraint::Percentage(100),
        ])
        .split(vertical[0]);

        let left_chunks =
            Layout::vertical([Constraint::Length(3), Constraint::Min(1)]).split(horizontal[0]);
        frame.render_widget(label, left_chunks[0]);

        self.areas = Some(Areas {
            tab_list: left_chunks[1],
            list: horizontal[1],
        });

        let tabs = self
            .tabs
            .iter()
            .map(|tab| tab.name.as_str())
            .collect::<Vec<_>>();

        let (tab_hl_style, highlight_symbol) = if let Focus::TabList = self.focus {
            (
                Style::default().reversed().fg(self.theme.tab_color()),
                self.theme.tab_icon(),
            )
        } else if let Focus::Search = self.focus {
            (Style::reset(), "   ")
        } else {
            (
                Style::new().fg(self.theme.tab_color()),
                self.theme.tab_icon(),
            )
        };

        let tab_list = List::new(tabs)
            .block(Block::bordered().border_set(border::ROUNDED))
            .highlight_style(tab_hl_style)
            .highlight_symbol(highlight_symbol);
        frame.render_stateful_widget(tab_list, left_chunks[1], &mut self.current_tab);

        let chunks =
            Layout::vertical([Constraint::Length(3), Constraint::Min(1)]).split(horizontal[1]);

        self.filter.draw_searchbar(frame, chunks[0], &self.theme);

        let mut items: Vec<Line> = Vec::with_capacity(self.filter.item_list().len());

        if !self.at_root() {
            items.push(
                Line::from(format!("{}  ..", self.theme.dir_icon())).style(self.theme.dir_color()),
            );
        }

        items.extend(self.filter.item_list().iter().map(
            |ListEntry {
                 node, has_children, ..
             }| {
                let is_selected = self.selected_commands.contains(node);
                let (indicator, style) = if is_selected {
                    (self.theme.multi_select_icon(), Style::new().bold())
                } else {
                    let ms_style = if self.multi_select && !node.multi_select {
                        Style::new().fg(self.theme.multi_select_disabled_color())
                    } else {
                        Style::new()
                    };
                    ("", ms_style)
                };
                if *has_children {
                    Line::styled(
                        format!("{}  {}", self.theme.dir_icon(), node.name,),
                        self.theme.dir_color(),
                    )
                    .patch_style(style)
                } else {
                    let left_content =
                        format!("{}  {} {}", self.theme.cmd_icon(), node.name, indicator);
                    let right_content = format!("{} ", node.task_list);
                    let center_space = " ".repeat(
                        chunks[1].width as usize - left_content.len() - right_content.len(),
                    );
                    Line::styled(
                        format!("{}{}{}", left_content, center_space, right_content),
                        self.theme.cmd_color(),
                    )
                    .patch_style(style)
                }
            },
        ));

        let style = if let Focus::List = self.focus {
            Style::default().reversed()
        } else {
            Style::new()
        };

        let title = if self.multi_select {
            &format!("{}[Multi-Select] ", TITLE)
        } else {
            TITLE
        };

        #[cfg(feature = "tips")]
        let bottom_title = Line::from(format!(" {} ", self.tip))
            .bold()
            .blue()
            .centered();
        #[cfg(not(feature = "tips"))]
        let bottom_title = "";

        let task_list_title = Line::from(" Important Actions ").right_aligned();

        // Create the list widget with items
        let list = List::new(items)
            .highlight_style(style)
            .block(
                Block::bordered()
                    .border_set(border::ROUNDED)
                    .title(title)
                    .title(task_list_title)
                    .title_bottom(bottom_title),
            )
            .scroll_padding(1);
        frame.render_stateful_widget(list, chunks[1], &mut self.selection);

        match &mut self.focus {
            Focus::FloatingWindow(float) => float.draw(frame, chunks[1], &self.theme),
            Focus::ConfirmationPrompt(prompt) => prompt.draw(frame, chunks[1], &self.theme),
            _ => {}
        }

        frame.render_widget(keybind_para, vertical[1]);
    }

    pub fn handle_mouse(&mut self, event: &MouseEvent) -> bool {
        if !self.mouse_enabled {
            return true;
        }

        if !self.drawable {
            return true;
        }

        if matches!(self.focus, Focus::TabList | Focus::List) {
            let position = Position::new(event.column, event.row);
            let mouse_in_tab_list = self.areas.as_ref().unwrap().tab_list.contains(position);
            let mouse_in_list = self.areas.as_ref().unwrap().list.contains(position);

            match event.kind {
                MouseEventKind::Moved => {
                    if mouse_in_list {
                        self.focus = Focus::List
                    } else if mouse_in_tab_list {
                        self.focus = Focus::TabList
                    }
                }
                MouseEventKind::ScrollDown => {
                    if mouse_in_tab_list {
                        if self.current_tab.selected().unwrap() != self.tabs.len() - 1 {
                            self.current_tab.select_next();
                        }
                        self.refresh_tab();
                    } else if mouse_in_list {
                        self.selection.select_next()
                    }
                }
                MouseEventKind::ScrollUp => {
                    if mouse_in_tab_list {
                        if self.current_tab.selected().unwrap() != 0 {
                            self.current_tab.select_previous();
                        }
                        self.refresh_tab();
                    } else if mouse_in_list {
                        self.selection.select_previous()
                    }
                }
                _ => {}
            }
        }
        match &mut self.focus {
            Focus::FloatingWindow(float) => {
                float.handle_mouse_event(event);
            }
            Focus::ConfirmationPrompt(confirm) => {
                confirm.content.handle_mouse_event(event);
            }
            _ => {}
        }
        true
    }

    pub fn handle_key(&mut self, key: &KeyEvent) -> bool {
        // This should be defined first to allow closing
        // the application even when not drawable ( If terminal is small )
        // Exit on 'q' or 'Ctrl-c' input
        if matches!(self.focus, Focus::TabList | Focus::List)
            && (key.code == KeyCode::Char('q')
                || key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c'))
        {
            return false;
        }

        if matches!(self.focus, Focus::ConfirmationPrompt(_))
            && (key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c'))
        {
            return false;
        }

        // If UI is not drawable returning true will mark as the key handled
        if !self.drawable {
            return true;
        }

        // Handle key only when Tablist or List is focused
        // Prevents exiting the application even when a command is running
        // Add keys here which should work on both TabList and List
        if matches!(self.focus, Focus::TabList | Focus::List)
            && self.handle_tablist_and_list_keys(key)
        {
            return true;
        }

        match &mut self.focus {
            Focus::FloatingWindow(command) => {
                if command.handle_key_event(key) {
                    self.focus = Focus::List;
                }
            }

            Focus::ConfirmationPrompt(confirm) => {
                confirm.content.handle_key_event(key);
                match confirm.content.status {
                    ConfirmStatus::Abort => {
                        self.focus = Focus::List;
                        // selected command was pushed to selection list if multi-select was
                        // enabled, need to clear it to prevent state corruption
                        if !self.multi_select {
                            self.selected_commands.clear()
                        } else {
                            // Prevents non multi_selectable cmd from being pushed into the selected list
                            if let Some(node) = self.get_selected_node() {
                                if !node.multi_select {
                                    self.selected_commands.retain(|cmd| cmd.name != node.name);
                                }
                            }
                        }
                    }
                    ConfirmStatus::Confirm => self.handle_confirm_command(),
                    ConfirmStatus::None => {}
                }
            }

            Focus::Search => match self.filter.handle_key(key) {
                SearchAction::Exit => self.exit_search(),
                SearchAction::Update => self.update_items(),
                SearchAction::None => {}
            },

            Focus::TabList => match key.code {
                KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.focus = Focus::List,
                KeyCode::Char('j') | KeyCode::Down => self.scroll_tab_down(),
                KeyCode::Char('k') | KeyCode::Up => self.scroll_tab_up(),

                _ => {}
            },

            Focus::List if key.kind != KeyEventKind::Release => match key.code {
                KeyCode::Char('j') | KeyCode::Down => self.scroll_down(),
                KeyCode::Char('k') | KeyCode::Up => self.scroll_up(),
                KeyCode::Char('p') | KeyCode::Char('P') => self.enable_preview(),
                KeyCode::Char('d') | KeyCode::Char('D') => self.enable_description(),
                KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.handle_enter(),
                KeyCode::Char('h') | KeyCode::Left => self.go_back(),
                KeyCode::Char(' ') if self.multi_select => self.toggle_selection(),
                _ => {}
            },

            _ => (),
        };
        true
    }

    fn handle_tablist_and_list_keys(&mut self, key: &KeyEvent) -> bool {
        match key.code {
            KeyCode::Tab => self.scroll_tab_down(),
            KeyCode::BackTab => self.scroll_tab_up(),
            KeyCode::Char('/') => self.enter_search(),
            KeyCode::Char('g') | KeyCode::Char('G') => self.enable_task_list_guide(),
            KeyCode::Char('v') | KeyCode::Char('V') => self.toggle_multi_select(),
            KeyCode::Char('t') => self.theme.next(),
            KeyCode::Char('T') => self.theme.prev(),
            _ => return false,
        }
        true
    }

    fn scroll(&mut self, direction: ScrollDir) {
        let Some(selected) = self.selection.selected() else {
            return;
        };
        let list_len = if !self.at_root() {
            self.filter.item_list().len() + 1
        } else {
            self.filter.item_list().len()
        };

        if list_len == 0 {
            return;
        };

        let next_selection = match direction {
            ScrollDir::Up if selected == 0 => list_len - 1,
            ScrollDir::Down if selected >= list_len - 1 => 0,
            ScrollDir::Up => selected - 1,
            ScrollDir::Down => selected + 1,
        };
        self.selection.select(Some(next_selection));
    }

    fn scroll_up(&mut self) {
        self.scroll(ScrollDir::Up)
    }

    fn scroll_down(&mut self) {
        self.scroll(ScrollDir::Down)
    }

    fn toggle_multi_select(&mut self) {
        self.multi_select = !self.multi_select;
        if !self.multi_select {
            self.selected_commands.clear();
        }
    }

    fn toggle_selection(&mut self) {
        if let Some(node) = self.get_selected_node() {
            if node.multi_select {
                if self.selected_commands.contains(&node) {
                    self.selected_commands.retain(|c| c != &node);
                } else {
                    self.selected_commands.push(node);
                }
            }
        }
    }

    fn update_items(&mut self) {
        self.filter.update_items(
            &self.tabs,
            self.current_tab.selected().unwrap(),
            self.visit_stack.last().unwrap().0,
        );

        let len = self.filter.item_list().len();
        if len > 0 {
            let current = self.selection.selected().unwrap_or(0);
            self.selection.select(Some(current.min(len - 1)));
        } else {
            self.selection.select(None);
        }
    }

    /// Checks either the current tree node is the root node (can we go up the tree or no)
    /// Returns `true` if we can't go up the tree (we are at the tree root)
    /// else returns `false`
    pub fn at_root(&self) -> bool {
        self.visit_stack.len() == 1
    }

    fn go_back(&mut self) {
        if self.at_root() {
            self.focus = Focus::TabList;
        } else {
            self.enter_parent_directory();
        }
    }

    fn enter_parent_directory(&mut self) {
        if let Some((_, previous_position)) = self.visit_stack.pop() {
            self.selection.select(Some(previous_position));
            self.update_items();
        }
    }

    fn get_selected_node(&self) -> Option<Rc<ListNode>> {
        let mut selected_index = self.selection.selected().unwrap_or(0);

        if !self.at_root() {
            if selected_index == 0 {
                return None;
            } else {
                selected_index = selected_index.saturating_sub(1);
            }
        }

        if let Some(item) = self.filter.item_list().get(selected_index) {
            if !item.has_children {
                return Some(item.node.clone());
            }
        }
        None
    }

    fn get_selected_description(&self) -> Option<String> {
        self.get_selected_node()
            .map(|node| node.description.clone())
    }

    pub fn go_to_selected_dir(&mut self) {
        let selected_index = self.selection.selected().unwrap_or(0);

        if !self.at_root() && selected_index == 0 {
            self.enter_parent_directory();
            return;
        }

        let actual_index = if self.at_root() {
            selected_index
        } else {
            selected_index - 1
        };

        if let Some(item) = self.filter.item_list().get(actual_index) {
            if item.has_children {
                self.visit_stack.push((item.id, selected_index));
                self.selection.select(Some(0));
                self.update_items();
            }
        }
    }

    pub fn selected_item_is_dir(&self) -> bool {
        let mut selected_index = self.selection.selected().unwrap_or(0);

        if !self.at_root() {
            if selected_index == 0 {
                return false;
            } else {
                selected_index = selected_index.saturating_sub(1);
            }
        }

        self.filter
            .item_list()
            .get(selected_index)
            .is_some_and(|i| i.has_children)
    }

    pub fn selected_item_is_cmd(&self) -> bool {
        // Any item that is not a directory or up directory (..) must be a command
        self.selection.selected().is_some()
            && !(self.selected_item_is_up_dir() || self.selected_item_is_dir())
    }

    pub fn selected_item_is_up_dir(&self) -> bool {
        let selected_index = self.selection.selected().unwrap_or(0);
        !self.at_root() && selected_index == 0
    }

    fn enable_preview(&mut self) {
        if let Some(list_node) = self.get_selected_node() {
            let preview_title = format!("[Preview] - {}", list_node.name.as_str());
            let preview = FloatingText::from_command(&list_node.command, &preview_title, false);
            self.spawn_float(preview, FLOAT_SIZE, FLOAT_SIZE);
        }
    }

    fn enable_description(&mut self) {
        if let Some(command_description) = self.get_selected_description() {
            if !command_description.is_empty() {
                let description =
                    FloatingText::new(command_description, "Command Description", true);
                self.spawn_float(description, FLOAT_SIZE, FLOAT_SIZE);
            }
        }
    }

    fn enable_task_list_guide(&mut self) {
        self.spawn_float(
            FloatingText::new(ACTIONS_GUIDE.to_string(), "Important Actions Guide", true),
            FLOAT_SIZE,
            FLOAT_SIZE,
        );
    }

    fn get_selected_item_type(&self) -> SelectedItem {
        if self.selected_item_is_up_dir() {
            SelectedItem::UpDir
        } else if self.selected_item_is_dir() {
            SelectedItem::Directory
        } else if self.selected_item_is_cmd() {
            SelectedItem::Command
        } else {
            SelectedItem::None
        }
    }

    fn handle_enter(&mut self) {
        match self.get_selected_item_type() {
            SelectedItem::UpDir => self.enter_parent_directory(),
            SelectedItem::Directory => self.go_to_selected_dir(),
            SelectedItem::Command => {
                if self.selected_commands.is_empty() {
                    if let Some(node) = self.get_selected_node() {
                        self.selected_commands.push(node);
                    }
                }

                if self.skip_confirmation {
                    self.handle_confirm_command();
                } else {
                    self.spawn_confirmprompt();
                }
            }
            SelectedItem::None => {}
        }
    }

    fn handle_confirm_command(&mut self) {
        let commands: Vec<&Command> = self
            .selected_commands
            .iter()
            .map(|node| &node.command)
            .collect();

        let command = RunningCommand::new(&commands);
        self.spawn_float(command, FLOAT_SIZE, FLOAT_SIZE);
        self.selected_commands.clear();
    }

    fn spawn_float<T: FloatContent + 'static>(&mut self, float: T, width: u16, height: u16) {
        self.focus = Focus::FloatingWindow(Float::new(Box::new(float), width, height));
    }

    fn enter_search(&mut self) {
        self.focus = Focus::Search;
        self.filter.activate_search();
        self.selection.select(None);
    }

    fn exit_search(&mut self) {
        self.selection.select(Some(0));
        self.focus = Focus::List;
        self.filter.deactivate_search();
        self.update_items();
    }

    fn refresh_tab(&mut self) {
        self.visit_stack = vec![(
            self.tabs[self.current_tab.selected().unwrap()]
                .tree
                .root()
                .id(),
            0usize,
        )];
        self.selection.select(Some(0));
        self.filter.clear_search();
        self.update_items();
    }

    fn scroll_tab_down(&mut self) {
        if self.current_tab.selected().unwrap() == self.tabs.len() - 1 {
            self.current_tab.select_first();
        } else {
            self.current_tab.select_next();
        }
        self.refresh_tab();
    }

    fn scroll_tab_up(&mut self) {
        if self.current_tab.selected().unwrap() == 0 {
            self.current_tab.select(Some(self.tabs.len() - 1));
        } else {
            self.current_tab.select_previous();
        }
        self.refresh_tab();
    }
}
