//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall

// locals
// Ext
use remotefs::fs::{File, UnixPex};
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::props::{PropPayload, PropValue, TextSpan};
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
use tuirealm::ratatui::widgets::Clear;
use tuirealm::{AttrValue, Attribute, Sub, SubClause, SubEventClause};
use unicode_width::UnicodeWidthStr;

use super::browser::{FileExplorerTab, FoundExplorerTab};
use super::components::ATTR_FILES;
use super::{Context, FileTransferActivity, Id, components};
use crate::explorer::FileSorting;
use crate::utils::ui::{Popup, Size};

impl FileTransferActivity {
    // -- init

    /// Initialize file transfer activity's view
    pub(super) fn init(&mut self) {
        // Mount local file explorer
        let local_explorer_background = self.theme().transfer_local_explorer_background;
        let local_explorer_foreground = self.theme().transfer_local_explorer_foreground;
        let local_explorer_highlighted = self.theme().transfer_local_explorer_highlighted;
        let remote_explorer_background = self.theme().transfer_remote_explorer_background;
        let remote_explorer_foreground = self.theme().transfer_remote_explorer_foreground;
        let remote_explorer_highlighted = self.theme().transfer_remote_explorer_highlighted;
        let key_color = self.theme().misc_keys;
        let log_panel = self.theme().transfer_log_window;
        let log_background = self.theme().transfer_log_background;
        assert!(
            self.app
                .mount(
                    Id::FooterBar,
                    Box::new(components::FooterBar::new(key_color)),
                    vec![]
                )
                .is_ok()
        );
        assert!(
            self.app
                .mount(
                    Id::ExplorerHostBridge,
                    Box::new(components::ExplorerLocal::new(
                        "",
                        &[],
                        local_explorer_background,
                        local_explorer_foreground,
                        local_explorer_highlighted
                    )),
                    vec![]
                )
                .is_ok()
        );
        assert!(
            self.app
                .mount(
                    Id::ExplorerRemote,
                    Box::new(components::ExplorerRemote::new(
                        "",
                        &[],
                        remote_explorer_background,
                        remote_explorer_foreground,
                        remote_explorer_highlighted
                    )),
                    vec![]
                )
                .is_ok()
        );
        assert!(
            self.app
                .mount(
                    Id::Log,
                    Box::new(components::Log::new(vec![], log_panel, log_background)),
                    vec![]
                )
                .is_ok()
        );
        // Load status bar
        self.refresh_local_status_bar();
        self.refresh_remote_status_bar();
        // Update components
        self.update_host_bridge_filelist();
        // self.update_remote_filelist();
        // Global listener
        self.mount_global_listener();
        // Give focus to local explorer
        assert!(self.app.active(&Id::ExplorerHostBridge).is_ok());
    }

    // -- view

    /// View gui
    pub(super) fn view(&mut self) {
        self.redraw = false;
        let mut context: Context = self.context.take().unwrap();
        let _ = context.terminal.raw_mut().draw(|f| {
            // Prepare chunks
            let body = Layout::default()
                .direction(Direction::Vertical)
                .constraints(
                    [
                        Constraint::Min(7),    // Body
                        Constraint::Length(1), // Footer
                    ]
                    .as_ref(),
                )
                .split(f.area());
            // main chunks
            let main_chunks = Layout::default()
                .direction(Direction::Vertical)
                .constraints(
                    [
                        Constraint::Percentage(70), // Explorer
                        Constraint::Percentage(30), // Log
                    ]
                    .as_ref(),
                )
                .split(body[0]);
            // Create explorer chunks
            let tabs_chunks = Layout::default()
                .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
                .direction(Direction::Horizontal)
                .split(main_chunks[0]);
            // Create log box chunks
            let bottom_chunks = Layout::default()
                .constraints([Constraint::Length(1), Constraint::Length(10)].as_ref())
                .direction(Direction::Vertical)
                .split(main_chunks[1]);
            // Create status bar chunks
            let status_bar_chunks = Layout::default()
                .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
                .direction(Direction::Horizontal)
                .horizontal_margin(1)
                .split(bottom_chunks[0]);
            // Draw footer
            self.app.view(&Id::FooterBar, f, body[1]);
            // Draw explorers
            // @! Local explorer (Find or default)
            if matches!(self.browser.found_tab(), Some(FoundExplorerTab::Local)) {
                self.app.view(&Id::ExplorerFind, f, tabs_chunks[0]);
            } else {
                self.app.view(&Id::ExplorerHostBridge, f, tabs_chunks[0]);
            }
            // @! Remote explorer (Find or default)
            if matches!(self.browser.found_tab(), Some(FoundExplorerTab::Remote)) {
                self.app.view(&Id::ExplorerFind, f, tabs_chunks[1]);
            } else {
                self.app.view(&Id::ExplorerRemote, f, tabs_chunks[1]);
            }
            // Draw log box
            self.app.view(&Id::Log, f, bottom_chunks[1]);
            // Draw status bar
            self.app
                .view(&Id::StatusBarHostBridge, f, status_bar_chunks[0]);
            self.app.view(&Id::StatusBarRemote, f, status_bar_chunks[1]);
            // @! Draw popups
            if self.app.mounted(&Id::FatalPopup) {
                let popup = Popup(
                    Size::Percentage(50),
                    self.calc_popup_height(Id::FatalPopup, f.area().width, f.area().height),
                )
                .draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::FatalPopup, f, popup);
            } else if self.app.mounted(&Id::CopyPopup) {
                let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::CopyPopup, f, popup);
            } else if self.app.mounted(&Id::ChmodPopup) {
                let popup = Popup(Size::Percentage(50), Size::Unit(12)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::ChmodPopup, f, popup);
            } else if self.app.mounted(&Id::FilterPopup) {
                let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::FilterPopup, f, popup);
            } else if self.app.mounted(&Id::GotoPopup) {
                let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::GotoPopup, f, popup);
            } else if self.app.mounted(&Id::MkdirPopup) {
                let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::MkdirPopup, f, popup);
            } else if self.app.mounted(&Id::NewfilePopup) {
                let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::NewfilePopup, f, popup);
            } else if self.app.mounted(&Id::OpenWithPopup) {
                let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::OpenWithPopup, f, popup);
            } else if self.app.mounted(&Id::RenamePopup) {
                let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::RenamePopup, f, popup);
            } else if self.app.mounted(&Id::SaveAsPopup) {
                let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::SaveAsPopup, f, popup);
            } else if self.app.mounted(&Id::SymlinkPopup) {
                let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::SymlinkPopup, f, popup);
            } else if self.app.mounted(&Id::ExecPopup) {
                let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::ExecPopup, f, popup);
            } else if self.app.mounted(&Id::FileInfoPopup) {
                let popup = Popup(Size::Percentage(50), Size::Percentage(50)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::FileInfoPopup, f, popup);
            } else if self.app.mounted(&Id::ProgressBarPartial) {
                let popup = Popup(Size::Percentage(50), Size::Percentage(20)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                let popup_chunks = Layout::default()
                    .direction(Direction::Vertical)
                    .constraints(
                        [
                            Constraint::Percentage(50), // Full
                            Constraint::Percentage(50), // Partial
                        ]
                        .as_ref(),
                    )
                    .split(popup);
                self.app.view(&Id::ProgressBarFull, f, popup_chunks[0]);
                self.app.view(&Id::ProgressBarPartial, f, popup_chunks[1]);
            } else if self.app.mounted(&Id::DeletePopup) {
                let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::DeletePopup, f, popup);
            } else if self.app.mounted(&Id::ReplacePopup) {
                // NOTE: handle extended / normal modes
                if self.is_radio_replace_extended() {
                    let popup = Popup(Size::Percentage(50), Size::Percentage(50)).draw_in(f.area());
                    f.render_widget(Clear, popup);
                    let popup_chunks = Layout::default()
                        .direction(Direction::Vertical)
                        .constraints(
                            [
                                Constraint::Percentage(85), // List
                                Constraint::Percentage(15), // Radio
                            ]
                            .as_ref(),
                        )
                        .split(popup);
                    self.app
                        .view(&Id::ReplacingFilesListPopup, f, popup_chunks[0]);
                    self.app.view(&Id::ReplacePopup, f, popup_chunks[1]);
                } else {
                    let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
                    f.render_widget(Clear, popup);
                    // make popup
                    self.app.view(&Id::ReplacePopup, f, popup);
                }
            } else if self.app.mounted(&Id::DisconnectPopup) {
                let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::DisconnectPopup, f, popup);
            } else if self.app.mounted(&Id::QuitPopup) {
                let popup = Popup(Size::Percentage(30), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::QuitPopup, f, popup);
            } else if self.app.mounted(&Id::WatchedPathsList) {
                let popup = Popup(Size::Percentage(60), Size::Percentage(50)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::WatchedPathsList, f, popup);
            } else if self.app.mounted(&Id::WatcherPopup) {
                let popup = Popup(Size::Percentage(60), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::WatcherPopup, f, popup);
            } else if self.app.mounted(&Id::SortingPopup) {
                let popup = Popup(Size::Percentage(50), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::SortingPopup, f, popup);
            } else if self.app.mounted(&Id::ErrorPopup) {
                let popup = Popup(
                    Size::Percentage(50),
                    self.calc_popup_height(Id::ErrorPopup, f.area().width, f.area().height),
                )
                .draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::ErrorPopup, f, popup);
            } else if self.app.mounted(&Id::WaitPopup) {
                let wait_popup_lines = self
                    .app
                    .query(&Id::WaitPopup, Attribute::Text)
                    .map(|x| x.map(|x| x.unwrap_payload().unwrap_vec().len()))
                    .unwrap_or_default()
                    .unwrap_or(1) as u16;

                let popup =
                    Popup(Size::Percentage(50), Size::Unit(2 + wait_popup_lines)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::WaitPopup, f, popup);
            } else if self.app.mounted(&Id::SyncBrowsingMkdirPopup) {
                let popup = Popup(Size::Percentage(60), Size::Unit(3)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::SyncBrowsingMkdirPopup, f, popup);
            } else if self.app.mounted(&Id::KeybindingsPopup) {
                let popup = Popup(Size::Percentage(50), Size::Percentage(80)).draw_in(f.area());
                f.render_widget(Clear, popup);
                // make popup
                self.app.view(&Id::KeybindingsPopup, f, popup);
            }
        });
        // Re-give context
        self.context = Some(context);
    }

    // -- partials

    /// Mount info box
    pub(super) fn mount_info<S: AsRef<str>>(&mut self, text: S) {
        // Mount
        let info_color = self.theme().misc_info_dialog;
        assert!(
            self.app
                .remount(
                    Id::ErrorPopup,
                    Box::new(components::ErrorPopup::new(text, info_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::ErrorPopup).is_ok());
    }

    /// Mount error box
    pub(super) fn mount_error<S: AsRef<str>>(&mut self, text: S) {
        // Mount
        let error_color = self.theme().misc_error_dialog;
        assert!(
            self.app
                .remount(
                    Id::ErrorPopup,
                    Box::new(components::ErrorPopup::new(text, error_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::ErrorPopup).is_ok());
    }

    /// Umount error message
    pub(super) fn umount_error(&mut self) {
        let _ = self.app.umount(&Id::ErrorPopup);
    }

    pub(super) fn mount_fatal<S: AsRef<str>>(&mut self, text: S) {
        self.umount_wait();
        // Mount
        let error_color = self.theme().misc_error_dialog;
        assert!(
            self.app
                .remount(
                    Id::FatalPopup,
                    Box::new(components::FatalPopup::new(text, error_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::FatalPopup).is_ok());
    }

    /// Umount fatal error message
    pub(super) fn umount_fatal(&mut self) {
        let _ = self.app.umount(&Id::FatalPopup);
    }

    pub(super) fn mount_wait<S: AsRef<str>>(&mut self, text: S) {
        let color = self.theme().misc_info_dialog;
        assert!(
            self.app
                .remount(
                    Id::WaitPopup,
                    Box::new(components::WaitPopup::new(text, color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::WaitPopup).is_ok());
    }

    pub(super) fn mount_walkdir_wait(&mut self) {
        let color = self.theme().misc_info_dialog;
        assert!(
            self.app
                .remount(
                    Id::WaitPopup,
                    Box::new(components::WalkdirWaitPopup::new(
                        "Scanning current directory…",
                        color
                    )),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::WaitPopup).is_ok());

        self.view();
    }

    pub(super) fn update_walkdir_entries(&mut self, entries: usize) {
        let text = format!("Scanning current directory… ({entries} items found)",);
        let _ = self.app.attr(
            &Id::WaitPopup,
            Attribute::Text,
            AttrValue::Payload(PropPayload::Vec(vec![
                PropValue::TextSpan(TextSpan::from(text)),
                PropValue::TextSpan(TextSpan::from("Press 'CTRL+C' to abort")),
            ])),
        );

        self.view();
    }

    pub(super) fn mount_blocking_wait<S: AsRef<str>>(&mut self, text: S) {
        self.mount_wait(text);
        self.view();
    }

    pub(super) fn umount_wait(&mut self) {
        let _ = self.app.umount(&Id::WaitPopup);
    }

    /// Mount quit popup
    pub(super) fn mount_quit(&mut self) {
        // Protocol
        let quit_color = self.theme().misc_quit_dialog;
        assert!(
            self.app
                .remount(
                    Id::QuitPopup,
                    Box::new(components::QuitPopup::new(quit_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::QuitPopup).is_ok());
    }

    /// Umount quit popup
    pub(super) fn umount_quit(&mut self) {
        let _ = self.app.umount(&Id::QuitPopup);
    }

    /// Mount disconnect popup
    pub(super) fn mount_disconnect(&mut self) {
        // Protocol
        let quit_color = self.theme().misc_quit_dialog;
        assert!(
            self.app
                .remount(
                    Id::DisconnectPopup,
                    Box::new(components::DisconnectPopup::new(quit_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::DisconnectPopup).is_ok());
    }

    /// Umount disconnect popup
    pub(super) fn umount_disconnect(&mut self) {
        let _ = self.app.umount(&Id::DisconnectPopup);
    }

    pub(super) fn mount_chmod(&mut self, mode: UnixPex, title: String) {
        // Mount
        let color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::ChmodPopup,
                    Box::new(components::ChmodPopup::new(mode, color, title)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::ChmodPopup).is_ok());
    }

    pub(super) fn umount_chmod(&mut self) {
        let _ = self.app.umount(&Id::ChmodPopup);
    }

    pub(super) fn umount_filter(&mut self) {
        let _ = self.app.umount(&Id::FilterPopup);
    }

    pub(super) fn mount_filter(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::FilterPopup,
                    Box::new(components::FilterPopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::FilterPopup).is_ok());
    }

    pub(super) fn mount_copy(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::CopyPopup,
                    Box::new(components::CopyPopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::CopyPopup).is_ok());
    }

    pub(super) fn umount_copy(&mut self) {
        let _ = self.app.umount(&Id::CopyPopup);
    }

    pub(super) fn mount_exec(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::ExecPopup,
                    Box::new(components::ExecPopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::ExecPopup).is_ok());
    }

    pub(super) fn umount_exec(&mut self) {
        let _ = self.app.umount(&Id::ExecPopup);
    }

    pub(super) fn mount_find(&mut self, msg: impl ToString, fuzzy_search: bool) {
        // Get color
        let (bg, fg, hg) = match self.browser.tab() {
            FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => (
                self.theme().transfer_local_explorer_background,
                self.theme().transfer_local_explorer_foreground,
                self.theme().transfer_local_explorer_highlighted,
            ),
            FileExplorerTab::Remote | FileExplorerTab::FindRemote => (
                self.theme().transfer_remote_explorer_background,
                self.theme().transfer_remote_explorer_foreground,
                self.theme().transfer_remote_explorer_highlighted,
            ),
        };

        // Mount component
        assert!(
            self.app
                .remount(
                    Id::ExplorerFind,
                    if fuzzy_search {
                        Box::new(components::ExplorerFuzzy::new(
                            msg.to_string(),
                            &[],
                            bg,
                            fg,
                            hg,
                        ))
                    } else {
                        Box::new(components::ExplorerFind::new(
                            msg.to_string(),
                            &[],
                            bg,
                            fg,
                            hg,
                        ))
                    },
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::ExplorerFind).is_ok());
    }

    pub(super) fn umount_find(&mut self) {
        let _ = self.app.umount(&Id::ExplorerFind);
    }

    pub(super) fn mount_goto(&mut self) {
        // get files
        let files = self
            .browser
            .explorer()
            .iter_files()
            .filter(|f| f.is_dir() || f.is_symlink())
            .map(|f| f.path().to_string_lossy().to_string())
            .collect::<Vec<String>>();

        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::GotoPopup,
                    Box::new(components::GotoPopup::new(input_color, files)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::GotoPopup).is_ok());
    }

    pub(super) fn update_goto(&mut self, files: Vec<String>) {
        let payload = files
            .into_iter()
            .map(PropValue::Str)
            .collect::<Vec<PropValue>>();

        let _ = self.app.attr(
            &Id::GotoPopup,
            Attribute::Custom(ATTR_FILES),
            AttrValue::Payload(PropPayload::Vec(payload)),
        );
    }

    pub(super) fn umount_goto(&mut self) {
        let _ = self.app.umount(&Id::GotoPopup);
    }

    pub(super) fn mount_mkdir(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::MkdirPopup,
                    Box::new(components::MkdirPopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::MkdirPopup).is_ok());
    }

    pub(super) fn umount_mkdir(&mut self) {
        let _ = self.app.umount(&Id::MkdirPopup);
    }

    pub(super) fn mount_newfile(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::NewfilePopup,
                    Box::new(components::NewfilePopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::NewfilePopup).is_ok());
    }

    pub(super) fn umount_newfile(&mut self) {
        let _ = self.app.umount(&Id::NewfilePopup);
    }

    pub(super) fn mount_openwith(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::OpenWithPopup,
                    Box::new(components::OpenWithPopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::OpenWithPopup).is_ok());
    }

    pub(super) fn umount_openwith(&mut self) {
        let _ = self.app.umount(&Id::OpenWithPopup);
    }

    pub(super) fn mount_rename(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::RenamePopup,
                    Box::new(components::RenamePopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::RenamePopup).is_ok());
    }

    pub(super) fn umount_rename(&mut self) {
        let _ = self.app.umount(&Id::RenamePopup);
    }

    pub(super) fn mount_saveas(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::SaveAsPopup,
                    Box::new(components::SaveAsPopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::SaveAsPopup).is_ok());
    }

    pub(super) fn umount_saveas(&mut self) {
        let _ = self.app.umount(&Id::SaveAsPopup);
    }

    pub(super) fn mount_progress_bar(&mut self, root_name: String) {
        let prog_color_full = self.theme().transfer_progress_bar_full;
        let prog_color_partial = self.theme().transfer_progress_bar_partial;
        assert!(
            self.app
                .remount(
                    Id::ProgressBarFull,
                    Box::new(components::ProgressBarFull::new(
                        0.0,
                        "",
                        &root_name,
                        prog_color_full
                    )),
                    vec![],
                )
                .is_ok()
        );
        assert!(
            self.app
                .remount(
                    Id::ProgressBarPartial,
                    Box::new(components::ProgressBarPartial::new(
                        0.0,
                        "",
                        "Please wait",
                        prog_color_partial
                    )),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::ProgressBarPartial).is_ok());
    }

    pub(super) fn umount_progress_bar(&mut self) {
        let _ = self.app.umount(&Id::ProgressBarPartial);
        let _ = self.app.umount(&Id::ProgressBarFull);
    }

    pub(super) fn mount_file_sorting(&mut self) {
        let sorting_color = self.theme().transfer_status_sorting;
        let sorting: FileSorting = match self.browser.tab() {
            FileExplorerTab::HostBridge => self.host_bridge().get_file_sorting(),
            FileExplorerTab::Remote => self.remote().get_file_sorting(),
            _ => return,
        };
        assert!(
            self.app
                .remount(
                    Id::SortingPopup,
                    Box::new(components::SortingPopup::new(sorting, sorting_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::SortingPopup).is_ok());
    }

    pub(super) fn umount_file_sorting(&mut self) {
        let _ = self.app.umount(&Id::SortingPopup);
    }

    pub(super) fn mount_radio_delete(&mut self) {
        let warn_color = self.theme().misc_warn_dialog;
        assert!(
            self.app
                .remount(
                    Id::DeletePopup,
                    Box::new(components::DeletePopup::new(warn_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::DeletePopup).is_ok());
    }

    pub(super) fn umount_radio_delete(&mut self) {
        let _ = self.app.umount(&Id::DeletePopup);
    }

    pub(super) fn mount_radio_watch(&mut self, watch: bool, local: &str, remote: &str) {
        let info_color = self.theme().misc_info_dialog;
        assert!(
            self.app
                .remount(
                    Id::WatcherPopup,
                    Box::new(components::WatcherPopup::new(
                        watch, local, remote, info_color
                    )),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::WatcherPopup).is_ok());
    }

    pub(super) fn umount_radio_watcher(&mut self) {
        let _ = self.app.umount(&Id::WatcherPopup);
    }

    pub(super) fn mount_watched_paths_list(&mut self, paths: &[std::path::PathBuf]) {
        let info_color = self.theme().misc_info_dialog;
        assert!(
            self.app
                .remount(
                    Id::WatchedPathsList,
                    Box::new(components::WatchedPathsList::new(paths, info_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::WatchedPathsList).is_ok());
    }

    pub(super) fn umount_watched_paths_list(&mut self) {
        let _ = self.app.umount(&Id::WatchedPathsList);
    }

    pub(super) fn mount_radio_replace(&mut self, file_name: &str) {
        let warn_color = self.theme().misc_warn_dialog;
        assert!(
            self.app
                .remount(
                    Id::ReplacePopup,
                    Box::new(components::ReplacePopup::new(Some(file_name), warn_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::ReplacePopup).is_ok());
    }

    pub(super) fn mount_radio_replace_many(&mut self, files: &[String]) {
        let warn_color = self.theme().misc_warn_dialog;
        assert!(
            self.app
                .remount(
                    Id::ReplacingFilesListPopup,
                    Box::new(components::ReplacingFilesListPopup::new(files, warn_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(
            self.app
                .remount(
                    Id::ReplacePopup,
                    Box::new(components::ReplacePopup::new(None, warn_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::ReplacePopup).is_ok());
    }

    /// Returns whether radio replace is in "extended" mode (for many files)
    pub(super) fn is_radio_replace_extended(&self) -> bool {
        self.app.mounted(&Id::ReplacingFilesListPopup)
    }

    pub(super) fn umount_radio_replace(&mut self) {
        let _ = self.app.umount(&Id::ReplacePopup);
        let _ = self.app.umount(&Id::ReplacingFilesListPopup); // NOTE: replace anyway
    }

    pub(super) fn mount_file_info(&mut self, file: &File) {
        assert!(
            self.app
                .remount(
                    Id::FileInfoPopup,
                    Box::new(components::FileInfoPopup::new(file)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::FileInfoPopup).is_ok());
    }

    pub(super) fn umount_file_info(&mut self) {
        let _ = self.app.umount(&Id::FileInfoPopup);
    }

    pub(super) fn refresh_local_status_bar(&mut self) {
        let sorting_color = self.theme().transfer_status_sorting;
        let hidden_color = self.theme().transfer_status_hidden;
        assert!(
            self.app
                .remount(
                    Id::StatusBarHostBridge,
                    Box::new(components::StatusBarLocal::new(
                        &self.browser,
                        sorting_color,
                        hidden_color
                    )),
                    vec![],
                )
                .is_ok()
        );
    }

    pub(super) fn refresh_remote_status_bar(&mut self) {
        let sorting_color = self.theme().transfer_status_sorting;
        let hidden_color = self.theme().transfer_status_hidden;
        let sync_color = self.theme().transfer_status_sync_browsing;
        assert!(
            self.app
                .remount(
                    Id::StatusBarRemote,
                    Box::new(components::StatusBarRemote::new(
                        &self.browser,
                        sorting_color,
                        hidden_color,
                        sync_color
                    )),
                    vec![],
                )
                .is_ok()
        );
    }

    pub(super) fn mount_symlink(&mut self) {
        let input_color = self.theme().misc_input_dialog;
        assert!(
            self.app
                .remount(
                    Id::SymlinkPopup,
                    Box::new(components::SymlinkPopup::new(input_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::SymlinkPopup).is_ok());
    }

    pub(super) fn umount_symlink(&mut self) {
        let _ = self.app.umount(&Id::SymlinkPopup);
    }

    pub(super) fn mount_sync_browsing_mkdir_popup(&mut self, dir_name: &str) {
        let color = self.theme().misc_info_dialog;
        assert!(
            self.app
                .remount(
                    Id::SyncBrowsingMkdirPopup,
                    Box::new(components::SyncBrowsingMkdirPopup::new(color, dir_name,)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::SyncBrowsingMkdirPopup).is_ok());
    }

    pub(super) fn umount_sync_browsing_mkdir_popup(&mut self) {
        let _ = self.app.umount(&Id::SyncBrowsingMkdirPopup);
    }

    /// Mount help
    pub(super) fn mount_help(&mut self) {
        let key_color = self.theme().misc_keys;
        assert!(
            self.app
                .remount(
                    Id::KeybindingsPopup,
                    Box::new(components::KeybindingsPopup::new(key_color)),
                    vec![],
                )
                .is_ok()
        );
        assert!(self.app.active(&Id::KeybindingsPopup).is_ok());
    }

    pub(super) fn umount_help(&mut self) {
        let _ = self.app.umount(&Id::KeybindingsPopup);
    }

    // -- dynamic size

    /// Given the id of the component to display and the width and height of the total area,
    /// returns the height in percentage to the entire area height, that the popup should have
    fn calc_popup_height(&self, id: Id, width: u16, height: u16) -> Size {
        // Get current text width
        let text_width = self
            .app
            .query(&id, tuirealm::Attribute::Text)
            .ok()
            .flatten()
            .map(|x| {
                x.unwrap_payload()
                    .unwrap_vec()
                    .into_iter()
                    .map(|x| x.unwrap_text_span().content)
                    .collect::<Vec<String>>()
                    .join("")
                    .width() as u16
            })
            .unwrap_or(0);
        // Calc real width of a row in the popup
        let row_width = (width / 2).saturating_sub(2);
        // Calc row height in percentage (1 : height = x : 100)
        let row_height_p = (100.0 / (height as f64)).ceil() as u16;
        // Get amount of required rows NOTE: + 2 because of margins
        let display_rows = ((text_width as f64) / (row_width as f64)).ceil() as u16 + 2;
        // Return height (row_height_p * display_rows)
        Size::Percentage(display_rows * row_height_p)
    }

    // -- global listener

    fn mount_global_listener(&mut self) {
        assert!(
            self.app
                .mount(
                    Id::GlobalListener,
                    Box::<components::GlobalListener>::default(),
                    vec![
                        Sub::new(
                            SubEventClause::Keyboard(KeyEvent {
                                code: Key::Esc,
                                modifiers: KeyModifiers::NONE,
                            }),
                            Self::no_popup_mounted_clause(),
                        ),
                        Sub::new(
                            SubEventClause::Keyboard(KeyEvent {
                                code: Key::Char('h'),
                                modifiers: KeyModifiers::NONE,
                            }),
                            Self::no_popup_mounted_clause(),
                        ),
                        Sub::new(
                            SubEventClause::Keyboard(KeyEvent {
                                code: Key::Function(1),
                                modifiers: KeyModifiers::NONE,
                            }),
                            Self::no_popup_mounted_clause(),
                        ),
                        Sub::new(
                            SubEventClause::Keyboard(KeyEvent {
                                code: Key::Function(10),
                                modifiers: KeyModifiers::NONE,
                            }),
                            Self::no_popup_mounted_clause(),
                        ),
                        Sub::new(
                            SubEventClause::Keyboard(KeyEvent {
                                code: Key::Char('q'),
                                modifiers: KeyModifiers::NONE,
                            }),
                            Self::no_popup_mounted_clause(),
                        ),
                        Sub::new(SubEventClause::WindowResize, SubClause::Always)
                    ]
                )
                .is_ok()
        );
    }

    /// Returns a sub clause which requires that no popup is mounted in order to be satisfied
    fn no_popup_mounted_clause() -> SubClause<Id> {
        tuirealm::subclause_and_not!(
            Id::CopyPopup,
            Id::DeletePopup,
            Id::DisconnectPopup,
            Id::ErrorPopup,
            Id::ExecPopup,
            Id::FatalPopup,
            Id::FileInfoPopup,
            Id::GotoPopup,
            Id::KeybindingsPopup,
            Id::MkdirPopup,
            Id::NewfilePopup,
            Id::OpenWithPopup,
            Id::ProgressBarFull,
            Id::ProgressBarPartial,
            Id::ExplorerFind,
            Id::QuitPopup,
            Id::RenamePopup,
            Id::ReplacePopup,
            Id::SaveAsPopup,
            Id::SortingPopup,
            Id::SyncBrowsingMkdirPopup,
            Id::SymlinkPopup,
            Id::WatcherPopup,
            Id::WatchedPathsList,
            Id::ChmodPopup,
            Id::WaitPopup,
            Id::FilterPopup
        )
    }
}
