use std::{
    fs::OpenOptions,
    io::{BufWriter, Write},
    sync::{Arc, atomic::AtomicBool},
    time::SystemTime,
};

use bollard::query_parameters::LogsOptions;
use cansi::v3::categorise_text;
use crossterm::{
    event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind},
    execute,
};
use futures_util::StreamExt;
use parking_lot::Mutex;
use ratatui::layout::Rect;
use tokio::sync::mpsc::{Receiver, Sender};
use uuid::Uuid;

mod message;
use crate::{
    app_data::{AppData, DockerCommand, Header, ScrollDirection},
    app_error::AppError,
    config,
    docker_data::DockerMessage,
    exec::{ExecMode, tty_readable},
    ui::{DeleteButton, GuiState, SelectablePanel, Status, Ui},
};
pub use message::InputMessages;

/// Handle all input events
#[derive(Debug)]
pub struct InputHandler {
    app_data: Arc<Mutex<AppData>>,
    docker_tx: Sender<DockerMessage>,
    keymap: config::Keymap,
    gui_state: Arc<Mutex<GuiState>>,
    is_running: Arc<AtomicBool>,
    mouse_capture: bool,
    rx: Receiver<InputMessages>,
}

impl InputHandler {
    /// Initialize self, and running the message handling loop
    pub async fn start(
        app_data: Arc<Mutex<AppData>>,
        docker_tx: Sender<DockerMessage>,
        gui_state: Arc<Mutex<GuiState>>,
        is_running: Arc<AtomicBool>,
        rx: Receiver<InputMessages>,
    ) {
        let keymap = app_data.lock().config.keymap.clone();
        let mut inner = Self {
            app_data,
            docker_tx,
            gui_state,
            is_running,
            keymap,
            rx,
            mouse_capture: true,
        };
        inner.message_handler().await;
    }

    /// check for incoming messages
    async fn message_handler(&mut self) {
        while let Some(message) = self.rx.recv().await {
            match message {
                InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await,
                InputMessages::MouseEvent((mouse_event, modifider)) => {
                    let status = self.gui_state.lock().get_status();
                    let contains = |s: Status| status.contains(&s);

                    if contains(Status::DeleteConfirm) {
                        self.button_intersect(mouse_event).await;
                    } else if !contains(Status::Error)
                        && !contains(Status::Help)
                        && !contains(Status::DeleteConfirm)
                        && !contains(Status::Filter)
                        && !contains(Status::SearchLogs)
                    {
                        // TODO handle state where you want to scroll log search results with the mouse wheel
                        self.mouse_press(mouse_event, modifider).await;
                    }
                }
            }
        }
    }

    /// Sort the containers by a given header
    fn sort(&self, selected_header: Header) {
        self.app_data.lock().set_sort_by_header(selected_header);
    }

    /// Set global is_running flag to false, *should* quit immediately
    fn quit(&self) {
        self.is_running
            .store(false, std::sync::atomic::Ordering::SeqCst);
    }

    /// This is executed from the Delete Confirm dialog, and will send an internal message to actually remove the given container
    async fn confirm_delete(&self) {
        let id = self.gui_state.lock().get_delete_container();
        if let Some(id) = id {
            self.docker_tx
                .send(DockerMessage::Control((DockerCommand::Delete, id)))
                .await
                .ok();
        }
    }

    /// This is executed from the Delete Confirm dialog, and will clear the delete_container information (removes id and closes panel)
    fn clear_delete(&self) {
        self.gui_state.lock().set_delete_container(None);
    }

    async fn inspect_key(&self) {
        self.app_data.lock().clear_inspect_data();
        let selected = self.app_data.lock().get_selected_container().cloned();
        if let Some(g) = selected {
            self.docker_tx.send(DockerMessage::Inspect(g.id)).await.ok();
        }
    }

    /// Validate that one can exec into a Docker container
    async fn exec_key(&self) {
        let is_oxker = self.app_data.lock().is_oxker();
        if !is_oxker && tty_readable() {
            let uuid = Uuid::new_v4();
            GuiState::start_loading_animation(&self.gui_state, uuid);
            let (sx, rx) = tokio::sync::oneshot::channel();
            self.docker_tx.send(DockerMessage::Exec(sx)).await.ok();

            if let Ok(docker) = rx.await {
                (ExecMode::new(&self.app_data, &docker).await).map_or_else(
                    || {
                        self.app_data.lock().set_error(
                            AppError::DockerExec,
                            &self.gui_state,
                            Status::Error,
                        );
                    },
                    |mode| {
                        self.gui_state.lock().set_exec_mode(mode);
                    },
                );
            }
            self.gui_state.lock().stop_loading_animation(uuid);
        }
    }

    /// Toggle the mouse capture (via input of the 'm' key)
    fn mouse_capture_key(&mut self) {
        let err = || {
            self.app_data.lock().set_error(
                AppError::MouseCapture(!self.mouse_capture),
                &self.gui_state,
                Status::Error,
            );
        };
        if self.mouse_capture {
            if execute!(std::io::stdout(), DisableMouseCapture).is_ok() {
                self.gui_state
                    .lock()
                    .set_info_box("✖ mouse capture disabled");
            } else {
                err();
            }
        } else if Ui::enable_mouse_capture().is_ok() {
            self.gui_state
                .lock()
                .set_info_box("✓ mouse capture enabled");
        } else {
            err();
        }

        self.mouse_capture = !self.mouse_capture;
    }

    /// Save the currently selected containers logs into a `[container_name]_[timestamp].log` file
    async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
        let args = self.app_data.lock().config.clone();
        let container = self.app_data.lock().get_selected_container_id_state_name();
        if let Some((id, _, name)) = container
            && let Some(log_path) = args.dir_save
        {
            let (sx, rx) = tokio::sync::oneshot::channel();
            self.docker_tx.send(DockerMessage::Exec(sx)).await?;

            let now = SystemTime::now()
                .duration_since(SystemTime::UNIX_EPOCH)
                .map_or(0, |i| i.as_secs());

            let path = log_path.join(format!("{name}_{now}.log"));

            let options = Some(LogsOptions {
                stderr: true,
                stdout: true,
                timestamps: args.show_timestamp,
                since: 0,
                ..Default::default()
            });
            let mut logs = rx.await?.logs(id.get(), options);
            let mut output = vec![];

            while let Some(Ok(value)) = logs.next().await {
                let data = value.to_string();
                if !data.trim().is_empty() {
                    output.push(
                        categorise_text(&data)
                            .into_iter()
                            .map(|i| i.text)
                            .collect::<String>(),
                    );
                }
            }
            if !output.is_empty() {
                let mut stream = BufWriter::new(
                    OpenOptions::new()
                        .read(true)
                        .write(true)
                        .create(true)
                        .truncate(true)
                        .open(&path)?,
                );

                for line in &output {
                    stream.write_all(line.as_bytes())?;
                }
                stream.flush()?;

                self.gui_state
                    .lock()
                    .set_info_box(&format!("saved to {}", path.display()));
            }
        }
        Ok(())
    }

    /// Attempt to save the currently selected container logs to a file
    async fn save_key(&self) {
        let status = self.gui_state.lock().get_status();
        let contains = |s: Status| status.contains(&s);

        if !contains(Status::Logs) {
            self.gui_state.lock().status_push(Status::Logs);
            let uuid = Uuid::new_v4();
            GuiState::start_loading_animation(&self.gui_state, uuid);
            if self.save_logs().await.is_err() {
                self.app_data.lock().set_error(
                    AppError::DockerLogs,
                    &self.gui_state,
                    Status::Error,
                );
            }
            self.gui_state.lock().status_del(Status::Logs);
            self.gui_state.lock().stop_loading_animation(uuid);
        }
    }

    /// Send docker command, if the Commands panel is selected
    async fn enter_key(&self) {
        // This isn't great, just means you can't send docker commands before full initialization of the program
        let panel = self.gui_state.lock().get_selected_panel();
        if panel == SelectablePanel::Commands {
            let option_command = self.app_data.lock().selected_docker_controls();

            if let Some(command) = option_command {
                // Poor way of disallowing commands to be sent to a containerised okxer
                if self.app_data.lock().is_oxker_in_container() {
                    return;
                }
                let option_id = self.app_data.lock().get_selected_container_id();
                if let Some(id) = option_id {
                    match command {
                        DockerCommand::Delete => self
                            .docker_tx
                            .send(DockerMessage::ConfirmDelete(id))
                            .await
                            .ok(),

                        _ => self
                            .docker_tx
                            .send(DockerMessage::Control((command, id)))
                            .await
                            .ok(),
                    };
                }
            }
        }
    }

    /// If keymap.scroll_modifier is pressed, return 10, else return 1, to speed up scrolling
    fn get_modifier_total(&self, modifier: KeyModifiers) -> u8 {
        if modifier == self.keymap.scroll_many {
            10
        } else {
            1
        }
    }

    fn inspect_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) {
        for _ in 0..self.get_modifier_total(modifier) {
            self.gui_state.lock().set_inspect_offset(sd);
        }
    }

    // fn inspect_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) {
    //     for _ in 0..self.get_modifier_total(modifier) {
    //         self.gui_state.lock().set_inspect_offset(sd);
    //     }
    // }

    fn logs_horizontal_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) {
        let panel = self.gui_state.lock().get_selected_panel();
        if panel == SelectablePanel::Logs {
            let count = self.get_modifier_total(modifier);
            let width = self.gui_state.lock().get_screen_width();
            self.app_data
                .lock()
                .logs_horizontal_scroll(sd, width, count);
        }
    }

    /// Change the the "next" selectable panel
    /// If no containers, and on Commands panel, skip to next panel, as Commands panel isn't visible in this state
    fn next_panel_key(&self) {
        self.gui_state.lock().selectable_panel_next(&self.app_data);
    }

    /// Change to previously selected panel
    /// Need to skip the commands planel if there no are current containers running
    fn previous_panel_key(&self) {
        self.gui_state
            .lock()
            .selectable_panel_previous(&self.app_data);
    }

    fn scroll_start_key(&self) {
        let selected_panel = self.gui_state.lock().get_selected_panel();
        match selected_panel {
            SelectablePanel::Containers => self.app_data.lock().containers_start(),
            SelectablePanel::Logs => self.app_data.lock().log_start(),
            SelectablePanel::Commands => self.app_data.lock().docker_controls_start(),
        }
    }

    /// Go to end of the list of the currently selected panel
    fn scroll_end_key(&self) {
        let selected_panel = self.gui_state.lock().get_selected_panel();
        match selected_panel {
            SelectablePanel::Containers => self.app_data.lock().containers_end(),
            SelectablePanel::Logs => self.app_data.lock().log_end(),
            SelectablePanel::Commands => self.app_data.lock().docker_controls_end(),
        }
    }

    /// Actions to take when in Help status active
    fn handle_help(&mut self, key_code: KeyCode) {
        if self.keymap.clear.0 == key_code
            || self.keymap.clear.1 == Some(key_code)
            || self.keymap.toggle_help.0 == key_code
            || self.keymap.toggle_help.1 == Some(key_code)
        {
            self.gui_state.lock().status_del(Status::Help);
        }

        if self.keymap.toggle_mouse_capture.0 == key_code
            || self.keymap.toggle_mouse_capture.1 == Some(key_code)
        {
            self.mouse_capture_key();
        }
    }

    /// Actions to take when Error status active
    fn handle_error(&self, key_code: KeyCode) {
        if self.keymap.clear.0 == key_code || self.keymap.clear.1 == Some(key_code) {
            self.app_data.lock().remove_error();
            self.gui_state.lock().status_del(Status::Error);
        }
    }

    /// Actions to take when Delete status active
    async fn handle_delete(&self, key_code: KeyCode) {
        if self.keymap.delete_confirm.0 == key_code
            || self.keymap.delete_confirm.1 == Some(key_code)
        {
            self.confirm_delete().await;
        } else if self.keymap.delete_deny.0 == key_code
            || self.keymap.delete_deny.1 == Some(key_code)
            || self.keymap.clear.0 == key_code
            || self.keymap.clear.1 == Some(key_code)
        {
            self.clear_delete();
        }
    }

    /// Actions to take when Filter status active
    fn handle_search_logs(&self, key_code: KeyCode, modifier: KeyModifiers) {
        match key_code {
            KeyCode::Esc => {
                self.app_data.lock().logs_search_clear();
                self.gui_state.lock().status_del(Status::SearchLogs);
            }
            _ if KeyCode::Enter == key_code
                || self.keymap.log_search_mode.0 == key_code
                || self.keymap.log_search_mode.1 == Some(key_code) =>
            {
                self.gui_state.lock().status_del(Status::SearchLogs);
            }

            _ if self.keymap.scroll_back.0 == key_code
                || self.keymap.scroll_back.1 == Some(key_code) =>
            {
                self.logs_horizontal_scroll(modifier, &ScrollDirection::Up);
            }

            _ if self.keymap.scroll_forward.0 == key_code
                || self.keymap.scroll_forward.1 == Some(key_code) =>
            {
                self.logs_horizontal_scroll(modifier, &ScrollDirection::Down);
            }

            _ if self.keymap.scroll_down.0 == key_code => {
                self.app_data
                    .lock()
                    .log_search_scroll(&ScrollDirection::Down);
                // TODO should only do this is log_search_scroll returns some
                // Need to wait til app_data and gui_data is combined
                self.gui_state
                    .lock()
                    .set_logs_panel_selected(&self.app_data);
                //
            }

            _ if self.keymap.scroll_up.0 == key_code => {
                self.app_data.lock().log_search_scroll(&ScrollDirection::Up);
                // TODO should only do this is log_search_scroll returns some
                // Need to wait til app_data and gui_data is combined
                self.gui_state
                    .lock()
                    .set_logs_panel_selected(&self.app_data);
            }

            // handle up and down keys
            KeyCode::Backspace => {
                self.app_data.lock().log_search_pop();
            }
            KeyCode::Char(x) => {
                self.app_data.lock().log_search_push(x);
            }
            _ => (),
        }
    }

    /// Actions to take when Filter status active
    fn handle_inspect(&mut self, key_code: KeyCode, modifier: KeyModifiers) {
        match key_code {
            _ if self.keymap.inspect.0 == key_code
                || self.keymap.inspect.1 == Some(key_code)
                || self.keymap.clear.0 == key_code
                || self.keymap.clear.1 == Some(key_code) =>
            {
                self.app_data.lock().clear_inspect_data();
                self.gui_state.lock().clear_inspect_offset();
                self.gui_state.lock().status_del(Status::Inspect);
            }

            _ if self.keymap.scroll_down.0 == key_code
                || self.keymap.scroll_down.1 == Some(key_code) =>
            {
                self.inspect_scroll(modifier, &ScrollDirection::Down);
            }

            _ if self.keymap.scroll_up.0 == key_code
                || self.keymap.scroll_up.1 == Some(key_code) =>
            {
                self.inspect_scroll(modifier, &ScrollDirection::Up);
            }

            _ if self.keymap.scroll_forward.0 == key_code
                || self.keymap.scroll_forward.1 == Some(key_code) =>
            {
                self.inspect_scroll(modifier, &ScrollDirection::Right);
            }

            _ if self.keymap.scroll_back.0 == key_code
                || self.keymap.scroll_back.1 == Some(key_code) =>
            {
                self.inspect_scroll(modifier, &ScrollDirection::Left);
            }

            _ if self.keymap.toggle_mouse_capture.0 == key_code
                || self.keymap.toggle_mouse_capture.1 == Some(key_code) =>
            {
                self.mouse_capture_key();
            }
            _ if self.keymap.scroll_start.0 == key_code
                || self.keymap.scroll_start.1 == Some(key_code) =>
            {
                self.gui_state.lock().clear_inspect_offset();
            }
            _ if self.keymap.scroll_end.0 == key_code
                || self.keymap.scroll_end.1 == Some(key_code) =>
            {
                self.gui_state.lock().set_inspect_offset_y_to_max();
            }
            _ => (),
        }
    }

    /// Actions to take when Filter status active
    fn handle_filter(&self, key_code: KeyCode) {
        match key_code {
            KeyCode::Esc => {
                self.app_data.lock().filter_term_clear();
                self.gui_state.lock().status_del(Status::Filter);
            }
            _ if KeyCode::Enter == key_code
                || self.keymap.filter_mode.0 == key_code
                || self.keymap.filter_mode.1 == Some(key_code) =>
            {
                self.gui_state.lock().status_del(Status::Filter);
            }
            KeyCode::Backspace => {
                self.app_data.lock().filter_term_pop();
            }
            KeyCode::Char(x) => {
                self.app_data.lock().filter_term_push(x);
            }
            KeyCode::Right => {
                self.app_data.lock().filter_by_next();
            }
            KeyCode::Left => {
                self.app_data.lock().filter_by_prev();
            }
            _ => (),
        }
    }

    /// Handle input that refers to the sorting of columns
    fn handle_sort(&self, key_code: KeyCode) {
        match key_code {
            _ if self.keymap.force_redraw.0 == key_code
                || self.keymap.force_redraw.1 == Some(key_code) =>
            {
                self.gui_state.lock().set_clear();
            }
            _ if self.keymap.sort_reset.0 == key_code
                || self.keymap.sort_reset.1 == Some(key_code) =>
            {
                self.app_data.lock().reset_sorted();
            }

            _ if self.keymap.sort_by_name.0 == key_code
                || self.keymap.sort_by_name.1 == Some(key_code) =>
            {
                self.sort(Header::Name);
            }

            _ if self.keymap.sort_by_state.0 == key_code
                || self.keymap.sort_by_state.1 == Some(key_code) =>
            {
                self.sort(Header::State);
            }

            _ if self.keymap.sort_by_status.0 == key_code
                || self.keymap.sort_by_status.1 == Some(key_code) =>
            {
                self.sort(Header::Status);
            }

            _ if self.keymap.sort_by_cpu.0 == key_code
                || self.keymap.sort_by_cpu.1 == Some(key_code) =>
            {
                self.sort(Header::Cpu);
            }
            _ if self.keymap.sort_by_memory.0 == key_code
                || self.keymap.sort_by_memory.1 == Some(key_code) =>
            {
                self.sort(Header::Memory);
            }
            _ if self.keymap.sort_by_id.0 == key_code
                || self.keymap.sort_by_id.1 == Some(key_code) =>
            {
                self.sort(Header::Id);
            }
            _ if self.keymap.sort_by_image.0 == key_code
                || self.keymap.sort_by_image.1 == Some(key_code) =>
            {
                self.sort(Header::Image);
            }

            _ if self.keymap.sort_by_rx.0 == key_code
                || self.keymap.sort_by_rx.1 == Some(key_code) =>
            {
                self.sort(Header::Rx);
            }

            _ if self.keymap.sort_by_tx.0 == key_code
                || self.keymap.sort_by_tx.1 == Some(key_code) =>
            {
                self.sort(Header::Tx);
            }
            _ => (),
        }
    }

    // Increase the log panel height
    fn log_panel_height_increase(&self) {
        self.gui_state.lock().log_height_increase();
    }

    // Decrease the log panel height
    fn log_panel_height_decrease(&self) {
        self.gui_state.lock().log_height_decrease();
    }

    // Toggle visibility of the log panel
    fn log_panel_toggle(&self) {
        self.gui_state.lock().toggle_show_logs();
    }

    /// Handle button presses in all other scenarios
    #[allow(clippy::cognitive_complexity)]
    async fn handle_others(&mut self, key_code: KeyCode, modifier: KeyModifiers) {
        self.handle_sort(key_code);
        // shift key plus arrows
        match key_code {
            _ if self.keymap.exec.0 == key_code || self.keymap.exec.1 == Some(key_code) => {
                self.exec_key().await;
            }

            _ if self.keymap.toggle_help.0 == key_code
                || self.keymap.toggle_help.1 == Some(key_code) =>
            {
                self.gui_state.lock().status_push(Status::Help);
            }

            _ if self.keymap.toggle_mouse_capture.0 == key_code
                || self.keymap.toggle_mouse_capture.1 == Some(key_code) =>
            {
                self.mouse_capture_key();
            }
            _ if self.keymap.log_section_height_decrease.0 == key_code
                || self.keymap.log_section_height_decrease.1 == Some(key_code) =>
            {
                self.log_panel_height_decrease();
            }

            _ if self.keymap.log_section_height_increase.0 == key_code
                || self.keymap.log_section_height_increase.1 == Some(key_code) =>
            {
                self.log_panel_height_increase();
            }

            _ if self.keymap.log_section_toggle.0 == key_code
                || self.keymap.log_section_toggle.1 == Some(key_code) =>
            {
                self.log_panel_toggle();
            }

            _ if self.keymap.save_logs.0 == key_code
                || self.keymap.save_logs.1 == Some(key_code) =>
            {
                self.save_key().await;
            }

            _ if self.keymap.inspect.0 == key_code || self.keymap.inspect.1 == Some(key_code) => {
                self.inspect_key().await;
            }

            _ if self.keymap.select_next_panel.0 == key_code
                || self.keymap.select_next_panel.1 == Some(key_code) =>
            {
                self.next_panel_key();
            }

            _ if self.keymap.select_previous_panel.0 == key_code
                || self.keymap.select_previous_panel.1 == Some(key_code) =>
            {
                self.previous_panel_key();
            }

            _ if self.keymap.scroll_start.0 == key_code
                || self.keymap.scroll_start.1 == Some(key_code) =>
            {
                self.scroll_start_key();
            }

            _ if self.keymap.scroll_end.0 == key_code
                || self.keymap.scroll_end.1 == Some(key_code) =>
            {
                self.scroll_end_key();
            }

            _ if self.keymap.scroll_up.0 == key_code
                || self.keymap.scroll_up.1 == Some(key_code) =>
            {
                self.scroll(modifier, &ScrollDirection::Up).await;
            }

            _ if self.keymap.scroll_down.0 == key_code
                || self.keymap.scroll_down.1 == Some(key_code) =>
            {
                self.scroll(modifier, &ScrollDirection::Down).await;
            }

            _ if self.keymap.filter_mode.0 == key_code
                || self.keymap.filter_mode.1 == Some(key_code) =>
            {
                self.gui_state.lock().status_push(Status::Filter);
                self.docker_tx
                    .send(DockerMessage::UpdateEverything)
                    .await
                    .ok();
            }

            _ if self.keymap.log_search_mode.0 == key_code
                || self.keymap.log_search_mode.1 == Some(key_code) =>
            {
                if !self.gui_state.lock().get_show_logs() {
                    self.gui_state.lock().toggle_show_logs();
                }
                self.gui_state.lock().status_push(Status::SearchLogs);
            }

            _ if self.keymap.scroll_back.0 == key_code
                || self.keymap.scroll_back.1 == Some(key_code) =>
            {
                self.logs_horizontal_scroll(modifier, &ScrollDirection::Up);
                // self.logs_back(modifier);
            }

            _ if self.keymap.scroll_forward.0 == key_code
                || self.keymap.scroll_forward.1 == Some(key_code) =>
            {
                self.logs_horizontal_scroll(modifier, &ScrollDirection::Down);
            }

            KeyCode::Enter => self.enter_key().await,
            _ => (),
        }
    }

    /// Handle keyboard button events
    async fn button_press(&mut self, key_code: KeyCode, key_modifier: KeyModifiers) {
        let status = self.gui_state.lock().get_status();
        let contains = |s: Status| status.contains(&s);

        let contains_error = contains(Status::Error);
        let contains_help = contains(Status::Help);
        let contains_exec = contains(Status::Exec);
        let contains_filter = contains(Status::Filter);
        let contains_delete = contains(Status::DeleteConfirm);
        let contains_search_logs = contains(Status::SearchLogs);
        let contains_inspect = contains(Status::Inspect);

        if !contains_exec {
            let is_q = || key_code == self.keymap.quit.0 || Some(key_code) == self.keymap.quit.1;
            if key_modifier == KeyModifiers::CONTROL && key_code == KeyCode::Char('c')
                || is_q() && !contains_filter && !contains_search_logs
            {
                // Always just quit on Ctrl + c/C or q/Q, unless in filter/search_logs mode, i.e. when user inmput can include the q key
                self.quit();
            }

            if contains_error {
                self.handle_error(key_code);
            } else if contains_help {
                self.handle_help(key_code);
            } else if contains_filter {
                self.handle_filter(key_code);
            } else if contains_search_logs {
                self.handle_search_logs(key_code, key_modifier);
            } else if contains_delete {
                self.handle_delete(key_code).await;
            } else if contains_inspect {
                self.handle_inspect(key_code, key_modifier);
            } else {
                self.handle_others(key_code, key_modifier).await;
            }
        }
    }

    /// Check if a button press interacts with either the yes or no buttons in the delete container confirm window
    async fn button_intersect(&self, mouse_event: MouseEvent) {
        if mouse_event.kind == MouseEventKind::Down(MouseButton::Left) {
            let intersect = self.gui_state.lock().get_intersect_button(Rect::new(
                mouse_event.column,
                mouse_event.row,
                1,
                1,
            ));

            if let Some(button) = intersect {
                match button {
                    DeleteButton::Confirm => self.confirm_delete().await,
                    DeleteButton::Cancel => self.clear_delete(),
                }
            }
        }
    }

    /// Handle mouse button events
    async fn mouse_press(&self, mouse_event: MouseEvent, modifier: KeyModifiers) {
        let status = self.gui_state.lock().get_status();

        if status.contains(&Status::Inspect) {
            match mouse_event.kind {
                MouseEventKind::ScrollDown => self.inspect_scroll(modifier, &ScrollDirection::Down),
                MouseEventKind::ScrollUp => self.inspect_scroll(modifier, &ScrollDirection::Up),
                MouseEventKind::ScrollRight => {
                    self.inspect_scroll(modifier, &ScrollDirection::Right)
                }
                MouseEventKind::ScrollLeft => self.inspect_scroll(modifier, &ScrollDirection::Left),
                _ => (),
            }
        } else if status.contains(&Status::Help) {
            let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
            let help_intersect = self.gui_state.lock().get_intersect_help(mouse_point);
            if help_intersect {
                self.gui_state.lock().status_del(Status::Help);
            }
        } else {
            match mouse_event.kind {
                MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Up).await,
                MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Down).await,
                // TODO left and right for log offsets
                MouseEventKind::Down(MouseButton::Left) => {
                    let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
                    let header = self.gui_state.lock().get_intersect_header(mouse_point);
                    if let Some(header) = header {
                        self.sort(header);
                    }
                    let help_intersect = self.gui_state.lock().get_intersect_help(mouse_point);
                    if help_intersect {
                        self.gui_state.lock().status_push(Status::Help);
                    }

                    self.gui_state.lock().check_panel_intersect(mouse_point);
                }
                _ => (),
            }
        }
    }

    /// Change state to next, depending which panel is currently in focus
    async fn scroll(&self, modifier: KeyModifiers, scroll: &ScrollDirection) {
        let status = self.gui_state.lock().get_status();
        if status.contains(&Status::SearchLogs) {
            self.app_data.lock().log_search_scroll(scroll);
        } else {
            let selected_panel = self.gui_state.lock().get_selected_panel();
            let multiplier = self.get_modifier_total(modifier);
            match selected_panel {
                SelectablePanel::Containers => {
                    self.app_data.lock().containers_scroll(scroll, multiplier);
                }
                SelectablePanel::Logs => {
                    self.app_data.lock().log_scroll(scroll, multiplier);
                }
                SelectablePanel::Commands => self.app_data.lock().docker_controls_scroll(scroll),
            }
            self.docker_tx
                .send(DockerMessage::UpdateSelectedLog)
                .await
                .ok();
        }
    }
}
