/*
 * meli
 *
 * Copyright 2017-2018 Manos Pitsidianakis
 *
 * This file is part of meli.
 *
 * meli is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * meli is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with meli. If not, see <http://www.gnu.org/licenses/>.
 */

use super::*;
use crate::conf::accounts::JobRequest;
use crate::types::segment_tree::SegmentTree;
use melib::backends::EnvelopeHashBatch;
use smallvec::SmallVec;
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::ops::{Deref, DerefMut};

use mailbox_list::MailboxList;

#[derive(Debug, Copy, PartialEq, Clone)]
pub enum Modifier {
    SymmetricDifference,
    Union,
    Difference,
    Intersection,
}

impl Default for Modifier {
    fn default() -> Self {
        Modifier::SymmetricDifference
    }
}

#[derive(Debug, Default, Clone)]
pub struct DataColumns {
    pub columns: [CellBuffer; 12],
    pub widths: [usize; 12], // widths of columns calculated in first draw and after size changes
    pub segment_tree: [SegmentTree; 12],
}

#[derive(Debug, Default)]
/// Save theme colors to avoid looking them up again and again from settings
struct ColorCache {
    theme_default: ThemeAttribute,

    unseen: ThemeAttribute,
    highlighted: ThemeAttribute,
    selected: ThemeAttribute,
    even: ThemeAttribute,
    odd: ThemeAttribute,
    even_unseen: ThemeAttribute,
    even_highlighted: ThemeAttribute,
    even_selected: ThemeAttribute,
    odd_unseen: ThemeAttribute,
    odd_highlighted: ThemeAttribute,
    odd_selected: ThemeAttribute,
    attachment_flag: ThemeAttribute,
    thread_snooze_flag: ThemeAttribute,
    tag_default: ThemeAttribute,

    /* Conversations */
    subject: ThemeAttribute,
    from: ThemeAttribute,
    date: ThemeAttribute,
    padding: ThemeAttribute,
    unseen_padding: ThemeAttribute,
}

#[derive(Debug)]
pub struct EntryStrings {
    pub date: DateString,
    pub subject: SubjectString,
    pub flag: FlagString,
    pub from: FromString,
    pub tags: TagString,
}

macro_rules! column_str {
    (struct $name:ident($($t:ty),+)) => {
        #[derive(Debug)]
        pub struct $name($(pub $t),+);

        impl Deref for $name {
            type Target = String;
            fn deref(&self) -> &String {
                &self.0
            }
        }
        impl DerefMut for $name {
            fn deref_mut(&mut self) -> &mut String {
                &mut self.0
            }
        }
    };
}

column_str!(struct DateString(String));
column_str!(struct FromString(String));
column_str!(struct SubjectString(String));
column_str!(struct FlagString(String));
column_str!(struct TagString(String, SmallVec<[Option<Color>; 8]>));

#[derive(Debug)]
struct AccountMenuEntry {
    name: String,
    hash: AccountHash,
    index: usize,
    entries: SmallVec<[(usize, u32, bool, MailboxHash); 16]>,
}

#[derive(PartialEq, Debug)]
enum ListingFocus {
    Menu,
    Mailbox,
}

#[derive(PartialEq, Copy, Clone, Debug)]
enum MenuEntryCursor {
    Status,
    Mailbox(usize),
}

#[derive(PartialEq, Copy, Clone, Debug)]
enum ShowMenuScrollbar {
    Never,
    True,
    False,
}

#[derive(Debug)]
pub struct Listing {
    mailbox_list: MailboxList,
    accounts: Vec<AccountMenuEntry>,
    status: Option<AccountStatus>,
    dirty: bool,
    visible: bool,
    cursor_pos: (usize, MenuEntryCursor),
    menu_cursor_pos: (usize, MenuEntryCursor),
    menu_content: CellBuffer,
    menu_scrollbar_show_timer: crate::jobs::Timer,
    show_menu_scrollbar: ShowMenuScrollbar,
    startup_checks_rate: RateLimit,
    id: ComponentId,
    theme_default: ThemeAttribute,

    sidebar_divider: char,
    sidebar_divider_theme: ThemeAttribute,

    menu_visibility: bool,
    cmd_buf: String,
    /// This is the width of the right container to the entire width.
    ratio: usize, // right/(container width) * 100
    menu_width: WidgetWidth,
    focus: ListingFocus,
}

impl fmt::Display for Listing {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "mail")
    }
}

impl Component for Listing {
    fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
        if !self.is_dirty() {
            return;
        }
        if !is_valid_area!(area) {
            return;
        }
        let upper_left = upper_left!(area);
        let bottom_right = bottom_right!(area);
        let total_cols = get_x(bottom_right) - get_x(upper_left);

        let right_component_width = if self.menu_visibility {
            if self.focus == ListingFocus::Menu {
                (self.ratio * total_cols) / 100
            } else {
                match self.menu_width {
                    WidgetWidth::Set(ref mut v) | WidgetWidth::Hold(ref mut v) => {
                        if *v == 0 {
                            *v = 1;
                        } else if *v >= total_cols {
                            *v = total_cols.saturating_sub(2);
                        }
                        total_cols.saturating_sub(*v)
                    }
                    WidgetWidth::Unset => {
                        self.menu_width =
                            WidgetWidth::Set(total_cols - ((self.ratio * total_cols) / 100));
                        (self.ratio * total_cols) / 100
                    }
                }
            }
        } else {
            total_cols
        };
        let mid = get_x(bottom_right) - right_component_width;
        if self.dirty && mid != get_x(upper_left) {
            for i in get_y(upper_left)..=get_y(bottom_right) {
                grid[(mid, i)]
                    .set_ch(self.sidebar_divider)
                    .set_fg(self.sidebar_divider_theme.fg)
                    .set_bg(self.sidebar_divider_theme.bg)
                    .set_attrs(self.sidebar_divider_theme.attrs);
            }
            context
                .dirty_areas
                .push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right))));
        }

        let account_hash = self.accounts[self.cursor_pos.0].hash;
        if right_component_width == total_cols {
            if let Some(s) = self.status.as_mut() {
                s.draw(grid, area, context);
            } else {
                self.mailbox_list.draw(grid, area, context);
            }
        } else if right_component_width == 0 {
            self.draw_menu(grid, area, context);
        } else {
            self.draw_menu(
                grid,
                (upper_left, (mid.saturating_sub(1), get_y(bottom_right))),
                context,
            );
            if let Some(s) = self.status.as_mut() {
                s.draw(grid, (set_x(upper_left, mid + 1), bottom_right), context);
            } else {
                self.mailbox_list
                    .draw(grid, (set_x(upper_left, mid + 1), bottom_right), context);
            }
        }
        self.dirty = false;
    }

    fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
        match event {
            UIEvent::ConfigReload { old_settings: _ } => {
                self.theme_default = crate::conf::value(context, "theme_default");
                let account_hash = context.accounts[self.cursor_pos.0].hash();
                self.sidebar_divider =
                    *account_settings!(context[account_hash].listing.sidebar_divider);
                self.sidebar_divider_theme = conf::value(context, "mail.sidebar_divider");
                self.menu_content = CellBuffer::new_with_context(0, 0, None, context);
                self.set_dirty(true);
            }
            UIEvent::Timer(n) if *n == self.menu_scrollbar_show_timer.id() => {
                if self.show_menu_scrollbar == ShowMenuScrollbar::True {
                    self.show_menu_scrollbar = ShowMenuScrollbar::False;
                    self.set_dirty(true);
                    self.menu_content.empty();
                }
                return true;
            }
            UIEvent::StartupCheck(ref f) => {
                if self.mailbox_list.coordinates.1 == *f {
                    if !self.startup_checks_rate.tick() {
                        return false;
                    }
                }
            }
            UIEvent::Timer(n) if *n == self.startup_checks_rate.id() => {
                if self.startup_checks_rate.active {
                    self.startup_checks_rate.reset();
                    return self.process_event(
                        &mut UIEvent::StartupCheck(self.mailbox_list.coordinates.1),
                        context,
                    );
                }
            }
            UIEvent::AccountStatusChange(account_hash) => {
                let account_index: usize = context
                    .accounts
                    .get_index_of(account_hash)
                    .expect("Invalid account_hash in UIEventMailbox{Delete,Create}");
                if self.cursor_pos.0 == account_index {
                    self.change_account(context);
                } else {
                    self.accounts[account_index].entries = context.accounts[&*account_hash]
                        .list_mailboxes()
                        .into_iter()
                        .filter(|mailbox_node| {
                            context.accounts[&*account_hash][&mailbox_node.hash]
                                .ref_mailbox
                                .is_subscribed()
                        })
                        .map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
                        .collect::<_>();
                    self.set_dirty(true);
                    self.menu_content.empty();
                    context
                        .replies
                        .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
                            self.get_status(context),
                        )));
                }
            }
            UIEvent::MailboxDelete((account_hash, mailbox_hash))
            | UIEvent::MailboxCreate((account_hash, mailbox_hash)) => {
                let account_index = context
                    .accounts
                    .get_index_of(account_hash)
                    .expect("Invalid account_hash in UIEventMailbox{Delete,Create}");
                self.menu_content.empty();
                self.accounts[account_index].entries = context.accounts[&*account_hash]
                    .list_mailboxes()
                    .into_iter()
                    .filter(|mailbox_node| {
                        context.accounts[&*account_hash][&mailbox_node.hash]
                            .ref_mailbox
                            .is_subscribed()
                    })
                    .map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
                    .collect::<_>();
                let mut fallback = 0;
                if let MenuEntryCursor::Mailbox(ref mut cur) = self.cursor_pos.1 {
                    *cur = std::cmp::min(
                        self.accounts[self.cursor_pos.0]
                            .entries
                            .len()
                            .saturating_sub(1),
                        *cur,
                    );
                    fallback = *cur;
                }
                if self.mailbox_list.coordinates == (*account_hash, *mailbox_hash) {
                    self.mailbox_list.coordinates = (
                        self.accounts[self.cursor_pos.0].hash,
                        self.accounts[self.cursor_pos.0].entries[fallback].3,
                    );
                    self.mailbox_list.refresh_mailbox(context, true);
                }
                context
                    .replies
                    .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
                        self.get_status(context),
                    )));
                self.set_dirty(true);
                return true;
            }
            UIEvent::ChangeMode(UIMode::Normal) => {
                self.set_dirty(true);
            }
            UIEvent::Resize => {
                self.set_dirty(true);
            }
            UIEvent::Action(Action::ViewMailbox(ref idx)) => {
                if let Some((_, _, _, mailbox_hash)) =
                    self.accounts[self.cursor_pos.0].entries.get(*idx)
                {
                    let account_hash = self.accounts[self.cursor_pos.0].hash;
                    self.cursor_pos.1 = MenuEntryCursor::Mailbox(*idx);
                    self.status = None;
                    self.mailbox_list
                        .set_coordinates((account_hash, *mailbox_hash), context);
                    self.menu_content.empty();
                    self.set_dirty(true);
                }
                return true;
            }
            _ => {}
        }

        if self.focus == ListingFocus::Mailbox && self.status.is_some() {
            if let Some(s) = self.status.as_mut() {
                if s.process_event(event, context) {
                    return true;
                }
            }
        }
        if self.focus == ListingFocus::Mailbox
            && self.status.is_none()
            && self.mailbox_list.process_event(event, context)
        {
            return true;
        }

        let shortcuts = self.get_shortcuts(context);
        if self.focus == ListingFocus::Mailbox {
            match *event {
                UIEvent::Input(Key::Mouse(MouseEvent::Press(MouseButton::Left, x, _y)))
                    if self.menu_visibility =>
                {
                    match self.menu_width {
                        WidgetWidth::Hold(wx) | WidgetWidth::Set(wx)
                            if wx + 1 == usize::from(x) =>
                        {
                            self.menu_width = WidgetWidth::Hold(wx - 1);
                        }
                        WidgetWidth::Set(_) => return false,
                        WidgetWidth::Hold(x) => {
                            self.menu_width = WidgetWidth::Set(x);
                        }
                        WidgetWidth::Unset => return false,
                    }
                    self.set_dirty(true);
                    return true;
                }
                UIEvent::Input(Key::Mouse(MouseEvent::Hold(x, _y))) if self.menu_visibility => {
                    match self.menu_width {
                        WidgetWidth::Hold(ref mut hx) => {
                            *hx = usize::from(x).saturating_sub(1);
                        }
                        _ => return false,
                    }
                    self.set_dirty(true);
                    return true;
                }
                UIEvent::Input(Key::Mouse(MouseEvent::Release(x, _y))) if self.menu_visibility => {
                    match self.menu_width {
                        WidgetWidth::Hold(_) => {
                            self.menu_width = WidgetWidth::Set(usize::from(x).saturating_sub(1));
                        }
                        _ => return false,
                    }
                    self.set_dirty(true);
                    return true;
                }
                UIEvent::Input(Key::Left) if self.menu_visibility => {
                    self.focus = ListingFocus::Menu;
                    if self.show_menu_scrollbar != ShowMenuScrollbar::Never {
                        self.menu_scrollbar_show_timer.rearm();
                        self.show_menu_scrollbar = ShowMenuScrollbar::True;
                    }
                    self.ratio = 50;
                    self.set_dirty(true);
                }
                UIEvent::Input(ref k)
                    if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_mailbox"])
                        || shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_mailbox"]) =>
                {
                    let amount = if self.cmd_buf.is_empty() {
                        1
                    } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        amount
                    } else {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        return true;
                    };
                    let target = match k {
                        k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_mailbox"]) => {
                            match self.cursor_pos.1 {
                                MenuEntryCursor::Status => amount.saturating_sub(1),
                                MenuEntryCursor::Mailbox(idx) => idx + amount,
                            }
                        }
                        k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_mailbox"]) => {
                            match self.cursor_pos.1 {
                                MenuEntryCursor::Status => {
                                    return true;
                                }
                                MenuEntryCursor::Mailbox(idx) => {
                                    if idx >= amount {
                                        idx - amount
                                    } else {
                                        return true;
                                    }
                                }
                            }
                        }
                        _ => return true,
                    };
                    if self.accounts[self.cursor_pos.0]
                        .entries
                        .get(target)
                        .is_some()
                    {
                        self.cursor_pos.1 = MenuEntryCursor::Mailbox(target)
                    } else {
                        return true;
                    }
                    self.change_account(context);
                    return true;
                }
                UIEvent::Input(ref k)
                    if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_account"])
                        || shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_account"]) =>
                {
                    let amount = if self.cmd_buf.is_empty() {
                        1
                    } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        amount
                    } else {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        return true;
                    };
                    match k {
                        k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_account"]) => {
                            if self.cursor_pos.0 + amount < self.accounts.len() {
                                self.cursor_pos =
                                    (self.cursor_pos.0 + amount, MenuEntryCursor::Mailbox(0));
                            } else {
                                return true;
                            }
                        }
                        k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_account"]) => {
                            if self.cursor_pos.0 >= amount {
                                self.cursor_pos =
                                    (self.cursor_pos.0 - amount, MenuEntryCursor::Mailbox(0));
                            } else {
                                return true;
                            }
                        }
                        _ => return false,
                    }
                    self.change_account(context);

                    return true;
                }
                UIEvent::Input(ref k)
                    if shortcut!(
                        k == shortcuts[Listing::DESCRIPTION]["toggle_menu_visibility"]
                    ) =>
                {
                    self.menu_visibility = !self.menu_visibility;
                    self.set_dirty(true);
                }
                _ => {}
            }

            if self.status.is_none() {
                match event {
                    UIEvent::Action(ref action) => match action {
                        Action::Listing(ListingAction::SetPlain) => {
                            self.mailbox_list.set_style(IndexStyle::Plain, context);
                            return true;
                        }
                        Action::Listing(ListingAction::SetThreaded) => {
                            self.mailbox_list.set_style(IndexStyle::Threaded, context);
                            return true;
                        }
                        Action::Listing(ListingAction::SetCompact) => {
                            self.mailbox_list.set_style(IndexStyle::Compact, context);
                            return true;
                        }
                        Action::Listing(ListingAction::SetConversations) => {
                            self.mailbox_list
                                .set_style(IndexStyle::Conversations, context);
                            return true;
                        }
                        Action::Listing(ListingAction::Import(file_path, mailbox_path)) => {
                            let account = &mut context.accounts[self.cursor_pos.0];
                            if let Err(err) = account
                                .mailbox_by_path(&mailbox_path)
                                .and_then(|mailbox_hash| {
                                    Ok((
                                        std::fs::read(&file_path).chain_err_summary(|| {
                                            format!("Could not read {}", file_path.display())
                                        })?,
                                        mailbox_hash,
                                    ))
                                })
                                .and_then(|(bytes, mailbox_hash)| {
                                    account.save(&bytes, mailbox_hash, None)
                                })
                            {
                                context.replies.push_back(UIEvent::StatusEvent(
                                    StatusEvent::DisplayMessage(err.to_string()),
                                ));
                            }
                            return true;
                        }
                        Action::Listing(a @ ListingAction::SetSeen)
                        | Action::Listing(a @ ListingAction::SetUnseen)
                        | Action::Listing(a @ ListingAction::Delete)
                        | Action::Listing(a @ ListingAction::CopyTo(_))
                        | Action::Listing(a @ ListingAction::MoveTo(_))
                        | Action::Listing(a @ ListingAction::CopyToOtherAccount(_, _))
                        | Action::Listing(a @ ListingAction::MoveToOtherAccount(_, _))
                        | Action::Listing(a @ ListingAction::Tag(_)) => {
                            let focused = self.mailbox_list.get_focused_items(context);
                            self.mailbox_list.perform_action(context, focused, a);
                            let mut row_updates: SmallVec<[ThreadHash; 8]> = SmallVec::new();
                            for (k, v) in self.mailbox_list.selection().iter_mut() {
                                if *v {
                                    *v = false;
                                    row_updates.push(*k);
                                }
                            }
                        }
                        _ => {}
                    },
                    UIEvent::Input(ref key)
                        if shortcut!(key == shortcuts[Listing::DESCRIPTION]["scroll_up"]) =>
                    {
                        let amount = if self.cmd_buf.is_empty() {
                            1
                        } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
                            self.cmd_buf.clear();
                            self.mailbox_list.set_modifier_active(false);
                            context
                                .replies
                                .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                            amount
                        } else {
                            self.cmd_buf.clear();
                            self.mailbox_list.set_modifier_active(false);
                            context
                                .replies
                                .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                            return true;
                        };
                        self.mailbox_list.set_movement(PageMovement::Up(amount));
                        return true;
                    }
                    UIEvent::Input(ref key)
                        if shortcut!(key == shortcuts[Listing::DESCRIPTION]["scroll_down"]) =>
                    {
                        let amount = if self.cmd_buf.is_empty() {
                            1
                        } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
                            self.cmd_buf.clear();
                            self.mailbox_list.set_modifier_active(false);
                            context
                                .replies
                                .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                            amount
                        } else {
                            self.cmd_buf.clear();
                            self.mailbox_list.set_modifier_active(false);
                            context
                                .replies
                                .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                            return true;
                        };
                        self.mailbox_list.set_movement(PageMovement::Down(amount));
                        return true;
                    }
                    UIEvent::Input(ref key)
                        if shortcut!(key == shortcuts[Listing::DESCRIPTION]["prev_page"]) =>
                    {
                        let mult = if self.cmd_buf.is_empty() {
                            1
                        } else if let Ok(mult) = self.cmd_buf.parse::<usize>() {
                            self.cmd_buf.clear();
                            self.mailbox_list.set_modifier_active(false);
                            context
                                .replies
                                .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                            mult
                        } else {
                            self.cmd_buf.clear();
                            self.mailbox_list.set_modifier_active(false);
                            context
                                .replies
                                .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                            return true;
                        };
                        self.mailbox_list.set_movement(PageMovement::PageUp(mult));
                        return true;
                    }
                    UIEvent::Input(ref key)
                        if shortcut!(key == shortcuts[Listing::DESCRIPTION]["next_page"]) =>
                    {
                        let mult = if self.cmd_buf.is_empty() {
                            1
                        } else if let Ok(mult) = self.cmd_buf.parse::<usize>() {
                            self.cmd_buf.clear();
                            self.mailbox_list.set_modifier_active(false);
                            context
                                .replies
                                .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                            mult
                        } else {
                            self.cmd_buf.clear();
                            self.mailbox_list.set_modifier_active(false);
                            context
                                .replies
                                .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                            return true;
                        };
                        self.mailbox_list.set_movement(PageMovement::PageDown(mult));
                        return true;
                    }
                    UIEvent::Input(ref key) if *key == Key::Home => {
                        self.mailbox_list.set_movement(PageMovement::Home);
                        return true;
                    }
                    UIEvent::Input(ref key) if *key == Key::End => {
                        self.mailbox_list.set_movement(PageMovement::End);
                        return true;
                    }
                    UIEvent::Input(ref key)
                        if shortcut!(key == shortcuts[Listing::DESCRIPTION]["search"]) =>
                    {
                        context
                            .replies
                            .push_back(UIEvent::CmdInput(Key::Paste("search ".to_string())));
                        context
                            .replies
                            .push_back(UIEvent::ChangeMode(UIMode::Command));
                        return true;
                    }
                    UIEvent::Input(ref key)
                        if shortcut!(key == shortcuts[Listing::DESCRIPTION]["set_seen"]) =>
                    {
                        let mut event = UIEvent::Action(Action::Listing(ListingAction::SetSeen));
                        if self.process_event(&mut event, context) {
                            return true;
                        }
                    }
                    UIEvent::Input(ref key)
                        if shortcut!(key == shortcuts[Listing::DESCRIPTION]["refresh"]) =>
                    {
                        let account = &mut context.accounts[self.cursor_pos.0];
                        if let MenuEntryCursor::Mailbox(idx) = self.cursor_pos.1 {
                            if let Some(&mailbox_hash) = account.mailboxes_order.get(idx) {
                                if let Err(err) = account.refresh(mailbox_hash) {
                                    context.replies.push_back(UIEvent::Notification(
                                        Some("Could not refresh.".to_string()),
                                        err.to_string(),
                                        Some(NotificationType::Error(err.kind)),
                                    ));
                                }
                            }
                        }
                        return true;
                    }
                    UIEvent::Input(ref key)
                        if !self.mailbox_list.unfocused()
                            && shortcut!(
                                key == shortcuts[Listing::DESCRIPTION]["union_modifier"]
                            )
                            && self.mailbox_list.modifier_command().is_some() =>
                    {
                        self.mailbox_list
                            .set_modifier_command(Some(Modifier::Union));
                    }
                    UIEvent::Input(ref key)
                        if !self.mailbox_list.unfocused()
                            && shortcut!(
                                key == shortcuts[Listing::DESCRIPTION]["diff_modifier"]
                            )
                            && self.mailbox_list.modifier_command().is_some() =>
                    {
                        self.mailbox_list
                            .set_modifier_command(Some(Modifier::Difference));
                    }
                    UIEvent::Input(ref key)
                        if !self.mailbox_list.unfocused()
                            && shortcut!(
                                key == shortcuts[Listing::DESCRIPTION]["intersection_modifier"]
                            )
                            && self.mailbox_list.modifier_command().is_some() =>
                    {
                        self.mailbox_list
                            .set_modifier_command(Some(Modifier::Intersection));
                    }
                    _ => {}
                }
            }
        } else if self.focus == ListingFocus::Menu {
            match *event {
                UIEvent::Input(Key::Right) => {
                    self.focus = ListingFocus::Mailbox;
                    self.ratio = 90;
                    self.set_dirty(true);
                    return true;
                }
                UIEvent::Input(ref k)
                    if shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_mailbox"])
                        && self.menu_cursor_pos.1 == MenuEntryCursor::Status =>
                {
                    self.cursor_pos = self.menu_cursor_pos;
                    self.open_status(self.menu_cursor_pos.0, context);
                    self.set_dirty(true);
                    self.focus = ListingFocus::Mailbox;
                    self.ratio = 90;
                    return true;
                }
                UIEvent::Input(ref k)
                    if shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_mailbox"]) =>
                {
                    self.cursor_pos = self.menu_cursor_pos;
                    self.change_account(context);
                    self.focus = ListingFocus::Mailbox;
                    self.ratio = 90;
                    self.set_dirty(true);
                    context
                        .replies
                        .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
                            self.get_status(context),
                        )));
                    return true;
                }
                UIEvent::Input(ref k)
                    if shortcut!(k == shortcuts[Listing::DESCRIPTION]["scroll_up"])
                        || shortcut!(k == shortcuts[Listing::DESCRIPTION]["scroll_down"]) =>
                {
                    let mut amount = if self.cmd_buf.is_empty() {
                        1
                    } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        amount
                    } else {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        return true;
                    };
                    if shortcut!(k == shortcuts[Listing::DESCRIPTION]["scroll_up"]) {
                        while amount > 0 {
                            match self.menu_cursor_pos {
                                (
                                    ref mut account_cursor,
                                    ref mut entry_cursor @ MenuEntryCursor::Status,
                                ) => {
                                    if *account_cursor > 0 {
                                        *account_cursor -= 1;
                                        *entry_cursor = MenuEntryCursor::Mailbox(
                                            self.accounts[*account_cursor]
                                                .entries
                                                .len()
                                                .saturating_sub(1),
                                        );
                                    } else {
                                        return true;
                                    }
                                }
                                (_, MenuEntryCursor::Mailbox(ref mut mailbox_idx)) => {
                                    if *mailbox_idx > 0 {
                                        *mailbox_idx -= 1;
                                    } else {
                                        self.menu_cursor_pos.1 = MenuEntryCursor::Status;
                                    }
                                }
                            }

                            amount -= 1;
                        }
                    } else if shortcut!(k == shortcuts[Listing::DESCRIPTION]["scroll_down"]) {
                        while amount > 0 {
                            match self.menu_cursor_pos {
                                /* If current account has mailboxes, go to first mailbox */
                                (
                                    ref account_cursor,
                                    ref mut entry_cursor @ MenuEntryCursor::Status,
                                ) if !self.accounts[*account_cursor].entries.is_empty() => {
                                    *entry_cursor = MenuEntryCursor::Mailbox(0);
                                }
                                /* If current account has no mailboxes, go to next account */
                                (
                                    ref mut account_cursor,
                                    ref mut entry_cursor @ MenuEntryCursor::Status,
                                ) if *account_cursor + 1 < self.accounts.len() => {
                                    *account_cursor += 1;
                                    *entry_cursor = MenuEntryCursor::Status;
                                }
                                /* If current account has no mailboxes and there is no next account, return true */
                                (_, MenuEntryCursor::Status) => {
                                    return true;
                                }
                                (
                                    ref mut account_cursor,
                                    MenuEntryCursor::Mailbox(ref mut mailbox_idx),
                                ) => {
                                    if (*mailbox_idx + 1)
                                        < self.accounts[*account_cursor].entries.len()
                                    {
                                        *mailbox_idx += 1;
                                    } else if *account_cursor + 1 < self.accounts.len() {
                                        *account_cursor += 1;
                                        self.menu_cursor_pos.1 = MenuEntryCursor::Status;
                                    } else {
                                        return true;
                                    }
                                }
                            }

                            amount -= 1;
                        }
                    }
                    if self.show_menu_scrollbar != ShowMenuScrollbar::Never {
                        self.menu_scrollbar_show_timer.rearm();
                        self.show_menu_scrollbar = ShowMenuScrollbar::True;
                    }
                    self.menu_content.empty();
                    self.set_dirty(true);
                    return true;
                }
                UIEvent::Input(ref k)
                    if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_mailbox"])
                        || shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_mailbox"]) =>
                {
                    let amount = if self.cmd_buf.is_empty() {
                        1
                    } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        amount
                    } else {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        return true;
                    };
                    let target = match k {
                        k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_mailbox"]) => {
                            match self.menu_cursor_pos.1 {
                                MenuEntryCursor::Status => amount.saturating_sub(1),
                                MenuEntryCursor::Mailbox(idx) => idx + amount,
                            }
                        }
                        k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_mailbox"]) => {
                            match self.menu_cursor_pos.1 {
                                MenuEntryCursor::Status => {
                                    return true;
                                }
                                MenuEntryCursor::Mailbox(idx) => {
                                    if idx >= amount {
                                        idx - amount
                                    } else {
                                        return true;
                                    }
                                }
                            }
                        }
                        _ => return true,
                    };
                    if self.accounts[self.menu_cursor_pos.0]
                        .entries
                        .get(target)
                        .is_some()
                    {
                        self.menu_cursor_pos.1 = MenuEntryCursor::Mailbox(target)
                    } else {
                        return true;
                    }
                    if self.show_menu_scrollbar != ShowMenuScrollbar::Never {
                        self.menu_scrollbar_show_timer.rearm();
                        self.show_menu_scrollbar = ShowMenuScrollbar::True;
                    }
                    self.menu_content.empty();
                    return true;
                }
                UIEvent::Input(ref k)
                    if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_account"])
                        || shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_account"])
                        || shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_page"])
                        || shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_page"]) =>
                {
                    let amount = if self.cmd_buf.is_empty() {
                        1
                    } else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        amount
                    } else {
                        self.cmd_buf.clear();
                        self.mailbox_list.set_modifier_active(false);
                        context
                            .replies
                            .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                        return true;
                    };
                    match k {
                        k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_account"])
                            || shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_page"]) =>
                        {
                            if self.menu_cursor_pos.0 + amount < self.accounts.len() {
                                self.menu_cursor_pos =
                                    (self.menu_cursor_pos.0 + amount, MenuEntryCursor::Mailbox(0));
                            } else {
                                return true;
                            }
                        }
                        k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_account"])
                            || shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_page"]) =>
                        {
                            if self.menu_cursor_pos.0 >= amount {
                                self.menu_cursor_pos =
                                    (self.menu_cursor_pos.0 - amount, MenuEntryCursor::Mailbox(0));
                            } else {
                                return true;
                            }
                        }
                        _ => return false,
                    }
                    if self.show_menu_scrollbar != ShowMenuScrollbar::Never {
                        self.menu_scrollbar_show_timer.rearm();
                        self.show_menu_scrollbar = ShowMenuScrollbar::True;
                    }
                    self.menu_content.empty();
                    self.set_dirty(true);

                    return true;
                }
                _ => {}
            }
        }
        match *event {
            UIEvent::Input(ref k)
                if shortcut!(k == shortcuts[Listing::DESCRIPTION]["new_mail"]) =>
            {
                let account_hash = context.accounts[self.cursor_pos.0].hash();
                let composer = Composer::with_account(account_hash, context);
                context
                    .replies
                    .push_back(UIEvent::Action(Tab(New(Some(Box::new(composer))))));
                return true;
            }
            UIEvent::StartupCheck(_)
            | UIEvent::MailboxUpdate(_)
            | UIEvent::EnvelopeUpdate(_)
            | UIEvent::EnvelopeRename(_, _)
            | UIEvent::EnvelopeRemove(_, _) => {
                self.dirty = true;
                /* clear menu to force redraw */
                self.menu_content.empty();
                context
                    .replies
                    .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
                        self.get_status(context),
                    )));
            }
            UIEvent::Input(Key::Backspace) if !self.cmd_buf.is_empty() => {
                self.cmd_buf.pop();
                context
                    .replies
                    .push_back(UIEvent::StatusEvent(StatusEvent::BufSet(
                        self.cmd_buf.clone(),
                    )));
                return true;
            }
            UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) if !self.cmd_buf.is_empty() => {
                self.cmd_buf.clear();
                self.mailbox_list.set_modifier_active(false);
                context
                    .replies
                    .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
                return true;
            }
            UIEvent::Input(Key::Char(c)) if c >= '0' && c <= '9' => {
                self.cmd_buf.push(c);
                self.mailbox_list.set_modifier_active(true);
                context
                    .replies
                    .push_back(UIEvent::StatusEvent(StatusEvent::BufSet(
                        self.cmd_buf.clone(),
                    )));
                return true;
            }
            _ => {}
        }
        false
    }
    fn is_dirty(&self) -> bool {
        self.dirty
            || self
                .status
                .as_ref()
                .map(Component::is_dirty)
                .unwrap_or_else(|| self.mailbox_list.is_dirty())
    }
    fn set_dirty(&mut self, value: bool) {
        self.dirty = value;
        if let Some(s) = self.status.as_mut() {
            s.set_dirty(value);
        } else {
            self.mailbox_list.set_dirty(value);
        }
    }

    fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
        let mut map = if let Some(s) = self.status.as_ref() {
            s.get_shortcuts(context)
        } else {
            self.mailbox_list.get_shortcuts(context)
        };
        let mut config_map = context.settings.shortcuts.listing.key_values();
        if self.focus != ListingFocus::Menu {
            config_map.remove("open_mailbox");
        }
        map.insert(Listing::DESCRIPTION, config_map);

        map
    }

    fn id(&self) -> ComponentId {
        self.mailbox_list.id()
    }
    fn set_id(&mut self, id: ComponentId) {
        self.mailbox_list.set_id(id);
    }

    fn get_status(&self, context: &Context) -> String {
        let mailbox_hash = match self.cursor_pos.1 {
            MenuEntryCursor::Mailbox(idx) => {
                if let Some((_, _, _, mailbox_hash)) =
                    self.accounts[self.cursor_pos.0].entries.get(idx)
                {
                    *mailbox_hash
                } else {
                    return String::new();
                }
            }
            MenuEntryCursor::Status => {
                return format!("{} status", &self.accounts[self.cursor_pos.0].name)
            }
        };

        let account = &context.accounts[self.cursor_pos.0];
        use crate::conf::accounts::MailboxStatus;
        match account[&mailbox_hash].status {
            MailboxStatus::Available | MailboxStatus::Parsing(_, _) => {
                let (unseen, total) = account[&mailbox_hash]
                    .ref_mailbox
                    .count()
                    .ok()
                    .unwrap_or((0, 0));
                format!(
                    "Mailbox: {}, Messages: {}, New: {}{}",
                    account[&mailbox_hash].name(),
                    total,
                    unseen,
                    if account[&mailbox_hash].status.is_parsing() {
                        "(Loading...)"
                    } else {
                        ""
                    }
                )
            }
            MailboxStatus::Failed(_) | MailboxStatus::None => account[&mailbox_hash].status(),
        }
    }
}

impl Listing {
    pub const DESCRIPTION: &'static str = "listing";
    pub fn new(context: &mut Context) -> Self {
        let account_entries: Vec<AccountMenuEntry> = context
            .accounts
            .iter()
            .enumerate()
            .map(|(i, (h, a))| {
                let entries: SmallVec<[(usize, u32, bool, MailboxHash); 16]> = a
                    .list_mailboxes()
                    .into_iter()
                    .filter(|mailbox_node| a[&mailbox_node.hash].ref_mailbox.is_subscribed())
                    .map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
                    .collect::<_>();

                AccountMenuEntry {
                    name: a.name().to_string(),
                    hash: *h,
                    index: i,
                    entries,
                }
            })
            .collect();
        let first_account_hash = account_entries[0].hash;
        let mut ret = Listing {
            mailbox_list: MailboxList::new((first_account_hash, 0), context),
            accounts: account_entries,
            status: None,
            visible: true,
            dirty: true,
            cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
            menu_cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
            menu_content: CellBuffer::new_with_context(0, 0, None, context),
            menu_scrollbar_show_timer: context.job_executor.clone().create_timer(
                std::time::Duration::from_secs(0),
                std::time::Duration::from_millis(1200),
            ),
            show_menu_scrollbar: ShowMenuScrollbar::Never,
            startup_checks_rate: RateLimit::new(2, 1000, context.job_executor.clone()),
            theme_default: conf::value(context, "theme_default"),
            id: ComponentId::new_v4(),
            sidebar_divider: *account_settings!(
                context[first_account_hash].listing.sidebar_divider
            ),
            sidebar_divider_theme: conf::value(context, "mail.sidebar_divider"),
            menu_visibility: true,
            ratio: 90,
            menu_width: WidgetWidth::Unset,
            focus: ListingFocus::Mailbox,
            cmd_buf: String::with_capacity(4),
        };
        ret.change_account(context);
        ret
    }

    fn draw_menu(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
        clear_area(grid, area, self.theme_default);
        let total_height: usize = 3 * (self.accounts.len())
            + self
                .accounts
                .iter()
                .map(|entry| entry.entries.len() + 1)
                .sum::<usize>();
        let min_width: usize = 2 * width!(area);
        let (width, height) = self.menu_content.size();
        let cursor = match self.focus {
            ListingFocus::Mailbox => self.cursor_pos,
            ListingFocus::Menu => self.menu_cursor_pos,
        };
        if min_width > width || height < total_height || self.dirty {
            let _ = self.menu_content.resize(min_width * 2, total_height, None);
            let bottom_right = pos_dec(self.menu_content.size(), (1, 1));
            let mut y = 0;
            for a in 0..self.accounts.len() {
                if y > get_y(bottom_right) {
                    break;
                }
                y += self.print_account(((0, y), bottom_right), a, context);
                y += 3;
            }
        }

        let rows = height!(area);
        let (width, height) = self.menu_content.size();
        const SCROLLING_CONTEXT: usize = 3;
        let y_offset = (cursor.0)
            + self
                .accounts
                .iter()
                .take(cursor.0)
                .map(|entry| entry.entries.len() + 1)
                .sum::<usize>()
            + match cursor.1 {
                MenuEntryCursor::Status => 0,
                MenuEntryCursor::Mailbox(idx) => idx + 1,
            }
            + SCROLLING_CONTEXT;
        let skip_offset = if y_offset <= rows {
            0
        } else {
            rows * y_offset.wrapping_div(rows).saturating_sub(1) + y_offset.wrapping_rem(rows)
        };

        copy_area(
            grid,
            &self.menu_content,
            area,
            (
                (
                    0,
                    std::cmp::min((height - 1).saturating_sub(rows), skip_offset),
                ),
                (width - 1, std::cmp::min(skip_offset + rows, height - 1)),
            ),
        );
        if self.show_menu_scrollbar == ShowMenuScrollbar::True && total_height > rows {
            ScrollBar::default().set_show_arrows(true).draw(
                grid,
                (
                    pos_inc(upper_left!(area), (width!(area), 0)),
                    bottom_right!(area),
                ),
                context,
                /* position */
                skip_offset,
                /* visible_rows */
                rows,
                /* length */
                total_height,
            );
        }

        context.dirty_areas.push_back(area);
    }

    /*
     * Print a single account in the menu area.
     */
    fn print_account(&mut self, area: Area, aidx: usize, context: &mut Context) -> usize {
        debug_assert!(is_valid_area!(area));
        // Each entry and its index in the account
        let mailboxes: HashMap<MailboxHash, Mailbox> = context.accounts[self.accounts[aidx].index]
            .mailbox_entries
            .iter()
            .map(|(&hash, entry)| (hash, entry.ref_mailbox.clone()))
            .collect();

        let upper_left = upper_left!(area);
        let bottom_right = bottom_right!(area);

        let cursor = match self.focus {
            ListingFocus::Mailbox => self.cursor_pos,
            ListingFocus::Menu => self.menu_cursor_pos,
        };

        let must_highlight_account: bool = cursor.0 == self.accounts[aidx].index;

        let mut lines: Vec<(usize, usize, u32, bool, MailboxHash, Option<usize>)> = Vec::new();

        for (i, &(depth, indentation, has_sibling, mailbox_hash)) in
            self.accounts[aidx].entries.iter().enumerate()
        {
            if mailboxes[&mailbox_hash].is_subscribed() {
                match context.accounts[self.accounts[aidx].index][&mailbox_hash].status {
                    crate::conf::accounts::MailboxStatus::Failed(_) => {
                        lines.push((depth, i, indentation, has_sibling, mailbox_hash, None));
                    }
                    _ => {
                        lines.push((
                            depth,
                            i,
                            indentation,
                            has_sibling,
                            mailbox_hash,
                            mailboxes[&mailbox_hash].count().ok().map(|(v, _)| v),
                        ));
                    }
                }
            }
        }

        let account_attrs = if must_highlight_account {
            if cursor.1 == MenuEntryCursor::Status {
                let mut v = crate::conf::value(context, "mail.sidebar_highlighted");
                if !context.settings.terminal.use_color() {
                    v.attrs |= Attr::REVERSE;
                }
                v
            } else {
                crate::conf::value(context, "mail.sidebar_highlighted_account_name")
            }
        } else {
            crate::conf::value(context, "mail.sidebar_account_name")
        };

        /* Print account name first */
        write_string_to_grid(
            &self.accounts[aidx].name,
            &mut self.menu_content,
            account_attrs.fg,
            account_attrs.bg,
            account_attrs.attrs,
            area,
            None,
        );

        if lines.is_empty() {
            write_string_to_grid(
                "offline",
                &mut self.menu_content,
                Color::Byte(243),
                account_attrs.bg,
                account_attrs.attrs,
                (pos_inc(upper_left, (0, 1)), bottom_right),
                None,
            );
            return 0;
        }

        let lines_len = lines.len();
        let mut idx = 0;
        let mut branches = String::with_capacity(16);

        for y in get_y(upper_left) + 1..get_y(bottom_right) {
            if idx == lines_len {
                break;
            }
            let (att, index_att, unread_count_att) = if must_highlight_account {
                if match cursor.1 {
                    MenuEntryCursor::Mailbox(c) => c == idx,
                    _ => false,
                } {
                    let mut ret = (
                        crate::conf::value(context, "mail.sidebar_highlighted"),
                        crate::conf::value(context, "mail.sidebar_highlighted_index"),
                        crate::conf::value(context, "mail.sidebar_highlighted_unread_count"),
                    );

                    if !context.settings.terminal.use_color() {
                        ret.0.attrs |= Attr::REVERSE;
                        ret.1.attrs |= Attr::REVERSE;
                        ret.2.attrs |= Attr::REVERSE;
                    }
                    ret
                } else {
                    (
                        crate::conf::value(context, "mail.sidebar_highlighted_account"),
                        crate::conf::value(context, "mail.sidebar_highlighted_account_index"),
                        crate::conf::value(
                            context,
                            "mail.sidebar_highlighted_account_unread_count",
                        ),
                    )
                }
            } else {
                (
                    crate::conf::value(context, "mail.sidebar"),
                    crate::conf::value(context, "mail.sidebar_index"),
                    crate::conf::value(context, "mail.sidebar_unread_count"),
                )
            };

            let (depth, inc, indentation, has_sibling, mailbox_idx, count) = lines[idx];
            /* Calculate how many columns the mailbox index tags should occupy with right alignment,
             * eg.
             *  1
             *  2
             * ...
             *  9
             * 10
             */
            let total_mailbox_no_digits = {
                let mut len = lines_len;
                let mut ctr = 1;
                while len > 9 {
                    ctr += 1;
                    len /= 10;
                }
                ctr
            };

            let has_sibling_str: &str = account_settings!(
                context[self.accounts[aidx].hash]
                    .listing
                    .sidebar_mailbox_tree_has_sibling
            )
            .as_ref()
            .map(|s| s.as_str())
            .unwrap_or(" ");
            let no_sibling_str: &str = account_settings!(
                context[self.accounts[aidx].hash]
                    .listing
                    .sidebar_mailbox_tree_no_sibling
            )
            .as_ref()
            .map(|s| s.as_str())
            .unwrap_or(" ");

            let has_sibling_leaf_str: &str = account_settings!(
                context[self.accounts[aidx].hash]
                    .listing
                    .sidebar_mailbox_tree_has_sibling_leaf
            )
            .as_ref()
            .map(|s| s.as_str())
            .unwrap_or(" ");

            let no_sibling_leaf_str: &str = account_settings!(
                context[self.accounts[aidx].hash]
                    .listing
                    .sidebar_mailbox_tree_no_sibling_leaf
            )
            .as_ref()
            .map(|s| s.as_str())
            .unwrap_or(" ");

            let (x, _) = write_string_to_grid(
                &format!("{:>width$}", inc, width = total_mailbox_no_digits),
                &mut self.menu_content,
                index_att.fg,
                index_att.bg,
                index_att.attrs,
                (set_y(upper_left, y), bottom_right),
                None,
            );
            {
                branches.clear();
                branches.push_str(no_sibling_str);
                let leading_zeros = indentation.leading_zeros();
                let mut o = 1_u32.wrapping_shl(31_u32.saturating_sub(leading_zeros));
                for _ in 0..(32_u32.saturating_sub(leading_zeros)) {
                    if indentation & o > 0 {
                        branches.push_str(has_sibling_str);
                    } else {
                        branches.push_str(no_sibling_str);
                    }
                    o >>= 1;
                }
                if depth > 0 {
                    if has_sibling {
                        branches.push_str(has_sibling_leaf_str);
                    } else {
                        branches.push_str(no_sibling_leaf_str);
                    }
                }
            }
            let (x, _) = write_string_to_grid(
                &branches,
                &mut self.menu_content,
                att.fg,
                att.bg,
                att.attrs,
                ((x, y), bottom_right),
                None,
            );
            let (x, _) = write_string_to_grid(
                context.accounts[self.accounts[aidx].index].mailbox_entries[&mailbox_idx].name(),
                &mut self.menu_content,
                att.fg,
                att.bg,
                att.attrs,
                ((x, y), bottom_right),
                None,
            );

            /* Unread message count */
            let count_string = if let Some(c) = count {
                if c > 0 {
                    format!(" {}", c)
                } else {
                    String::new()
                }
            } else {
                " ...".to_string()
            };

            let (x, _) = write_string_to_grid(
                &count_string,
                &mut self.menu_content,
                unread_count_att.fg,
                unread_count_att.bg,
                unread_count_att.attrs
                    | if count.unwrap_or(0) > 0 {
                        Attr::BOLD
                    } else {
                        Attr::DEFAULT
                    },
                (
                    (
                        /* Hide part of mailbox name if need be to fit the message count */
                        std::cmp::min(x, get_x(bottom_right).saturating_sub(count_string.len())),
                        y,
                    ),
                    bottom_right,
                ),
                None,
            );
            for c in self.menu_content.row_iter(x..(get_x(bottom_right) + 1), y) {
                self.menu_content[c]
                    .set_fg(att.fg)
                    .set_bg(att.bg)
                    .set_attrs(att.attrs);
            }
            idx += 1;
        }
        if idx == 0 {
            0
        } else {
            idx - 1
        }
    }

    fn change_account(&mut self, context: &mut Context) {
        let account_hash = context.accounts[self.cursor_pos.0].hash();
        self.accounts[self.cursor_pos.0].entries = context.accounts[self.cursor_pos.0]
            .list_mailboxes()
            .into_iter()
            .filter(|mailbox_node| {
                context.accounts[self.cursor_pos.0][&mailbox_node.hash]
                    .ref_mailbox
                    .is_subscribed()
            })
            .map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
            .collect::<_>();
        match self.cursor_pos.1 {
            MenuEntryCursor::Mailbox(idx) => {
                /* Account might have no mailboxes yet if it's offline */
                if let Some((_, _, _, mailbox_hash)) =
                    self.accounts[self.cursor_pos.0].entries.get(idx)
                {
                    self.mailbox_list
                        .set_coordinates((account_hash, *mailbox_hash), context);
                } else {
                    self.mailbox_list
                        .set_coordinates((account_hash, 0), context);
                }
                self.status = None;
                context
                    .replies
                    .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
                        self.get_status(context),
                    )));
            }
            MenuEntryCursor::Status => {
                self.open_status(self.cursor_pos.0, context);
            }
        }
        self.sidebar_divider = *account_settings!(context[account_hash].listing.sidebar_divider);
        self.set_dirty(true);
        self.menu_cursor_pos = self.cursor_pos;
        /* clear menu to force redraw */
        self.menu_content.empty();
        if *account_settings!(context[account_hash].listing.show_menu_scrollbar) {
            self.show_menu_scrollbar = ShowMenuScrollbar::True;
            self.menu_scrollbar_show_timer.rearm();
        } else {
            self.show_menu_scrollbar = ShowMenuScrollbar::Never;
        }
    }

    fn open_status(&mut self, account_idx: usize, context: &mut Context) {
        self.status = Some(AccountStatus::new(account_idx, self.theme_default));
        self.menu_content.empty();
        context
            .replies
            .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
                self.get_status(context),
            )));
    }
}

mod mailbox_list {
    use super::*;
    use crate::jobs::{JobId, JoinHandle};
    use std::collections::HashSet;
    use std::convert::TryInto;

    const LOADING_STR: &str = "...";
    macro_rules! row_attr {
        ($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr  $(,)*) => {{
            ThemeAttribute {
                fg: if $highlighted {
                    if $even {
                        $color_cache.even_highlighted.fg
                    } else {
                        $color_cache.odd_highlighted.fg
                    }
                } else if $selected {
                    if $even {
                        $color_cache.even_selected.fg
                    } else {
                        $color_cache.odd_selected.fg
                    }
                } else if $unseen {
                    if $even {
                        $color_cache.even_unseen.fg
                    } else {
                        $color_cache.odd_unseen.fg
                    }
                } else if $even {
                    $color_cache.even.fg
                } else {
                    $color_cache.odd.fg
                },
                bg: if $highlighted {
                    if $even {
                        $color_cache.even_highlighted.bg
                    } else {
                        $color_cache.odd_highlighted.bg
                    }
                } else if $selected {
                    if $even {
                        $color_cache.even_selected.bg
                    } else {
                        $color_cache.odd_selected.bg
                    }
                } else if $unseen {
                    if $even {
                        $color_cache.even_unseen.bg
                    } else {
                        $color_cache.odd_unseen.bg
                    }
                } else if $even {
                    $color_cache.even.bg
                } else {
                    $color_cache.odd.bg
                },
                attrs: if $highlighted {
                    if $even {
                        $color_cache.even_highlighted.attrs
                    } else {
                        $color_cache.odd_highlighted.attrs
                    }
                } else if $selected {
                    if $even {
                        $color_cache.even_selected.attrs
                    } else {
                        $color_cache.odd_selected.attrs
                    }
                } else if $unseen {
                    if $even {
                        $color_cache.even_unseen.attrs
                    } else {
                        $color_cache.odd_unseen.attrs
                    }
                } else if $even {
                    $color_cache.even.attrs
                } else {
                    $color_cache.odd.attrs
                },
            }
        }};
    }

    #[derive(Debug)]
    pub enum JobState {
        Offline {
            message: String,
        },
        Loading {
            job_id: JobId,
        },
        Loaded,
        Filtering {
            query: String,
            join_handle: JoinHandle<Result<SmallVec<[EnvelopeHash; 512]>>>,
        },
        Searching {
            query: String,
            join_handle: JoinHandle<Result<SmallVec<[EnvelopeHash; 512]>>>,
        },
    }

    impl Default for JobState {
        fn default() -> JobState {
            JobState::Offline {
                message: "uninitialized".to_string(),
            }
        }
    }

    #[derive(Debug, Default)]
    pub struct MailboxListState {
        pub cursor_pos: usize,
        pub new_cursor_pos: usize,
        pub selection: HashMap<ThreadNodeHash, bool>,
        pub movement: Option<PageMovement>,
        pub modifier_active: bool,
        pub modifier_command: Option<Modifier>,
        pub style: IndexStyle,
        pub filter_term: Option<String>,
        pub search_term: Option<String>,
        pub sort: (SortField, SortOrder),
        pub subsort: (SortField, SortOrder),
        pub job_state: JobState,
        pub fetching_envelopes: HashMap<JobId, EnvelopeHashBatch>,
    }

    impl MailboxListState {
        fn new() -> Self {
            MailboxListState {
                cursor_pos: 1,
                sort: (Default::default(), Default::default()),
                subsort: (SortField::Date, SortOrder::Desc),
                ..MailboxListState::default()
            }
        }
    }

    #[derive(Debug)]
    pub struct Entry {
        pub index: usize,
        pub root_envelope_hash: EnvelopeHash,
        pub thread_node_hash: ThreadNodeHash,
        pub thread_hash: ThreadHash,
        pub unseen: bool,
        pub snoozed: bool,
        pub has_attachments: bool,
        pub strings: Option<EntryStrings>,
    }

    #[derive(Debug)]
    pub struct MailboxList {
        pub coordinates: (AccountHash, MailboxHash),
        pub cached_states: HashMap<(AccountHash, MailboxHash), MailboxListState>,
        pub state: MailboxListState,
        pub data_columns: DataColumns,
        pub rows_drawn: SegmentTree,
        color_cache: ColorCache,
        pub entries: HashMap<usize, Entry>,
        pub rows: Vec<(ThreadHash, ThreadNodeHash)>,
        pub unloaded_envelope_hashes: HashMap<EnvelopeHash, usize>,
        /* Store page size to know how many rows to prefetch */
        pub last_recorded_page_size: usize,
        pub length: usize,
        pub id: ComponentId,
        pub unfocused: bool,
        pub view: ThreadView,
        pub force_draw: bool,
        pub dirty: bool,
    }

    impl MailboxList {
        pub fn new(coordinates: (AccountHash, MailboxHash), context: &mut Context) -> Self {
            let mut ret = MailboxList {
                coordinates: (0, 0),
                cached_states: HashMap::default(),
                state: MailboxListState::new(),
                data_columns: DataColumns::default(),
                rows_drawn: SegmentTree::default(),
                entries: HashMap::default(),
                rows: Vec::new(),
                color_cache: ColorCache::default(),
                unloaded_envelope_hashes: HashMap::default(),
                length: 0,
                last_recorded_page_size: 25,
                unfocused: false,
                view: ThreadView::default(),
                force_draw: true,
                dirty: true,
                id: ComponentId::new_v4(),
            };
            for column in ret.data_columns.columns.iter_mut() {
                *column = CellBuffer::new_with_context(0, 0, None, context);
            }
            ret.set_coordinates(coordinates, context);
            ret.update_color_cache(context);
            ret
        }

        pub fn set_style(&mut self, new_value: IndexStyle, context: &Context) {
            if self.state.style == new_value {
                return;
            }
            self.state.style = new_value;
            self.update_color_cache(context);
            self.initialize_columns();
        }

        fn update_color_cache(&mut self, context: &Context) {
            match self.state.style {
                IndexStyle::Conversations => {
                    self.color_cache = ColorCache {
                        theme_default: crate::conf::value(context, "mail.listing.conversations"),
                        subject: crate::conf::value(context, "mail.listing.conversations.subject"),
                        from: crate::conf::value(context, "mail.listing.conversations.from"),
                        date: crate::conf::value(context, "mail.listing.conversations.date"),
                        selected: crate::conf::value(
                            context,
                            "mail.listing.conversations.selected",
                        ),
                        unseen: crate::conf::value(context, "mail.listing.conversations.unseen"),
                        highlighted: crate::conf::value(
                            context,
                            "mail.listing.conversations.highlighted",
                        ),
                        attachment_flag: crate::conf::value(
                            context,
                            "mail.listing.attachment_flag",
                        ),
                        thread_snooze_flag: crate::conf::value(
                            context,
                            "mail.listing.thread_snooze_flag",
                        ),
                        tag_default: crate::conf::value(context, "mail.listing.tag_default"),
                        ..self.color_cache
                    };

                    if !context.settings.terminal.use_color() {
                        self.color_cache.highlighted.attrs |= Attr::REVERSE;
                        self.color_cache.tag_default.attrs |= Attr::REVERSE;
                    }
                }
                IndexStyle::Compact => {
                    self.color_cache = ColorCache {
                        even_unseen: crate::conf::value(
                            context,
                            "mail.listing.compact.even_unseen",
                        ),
                        even_selected: crate::conf::value(
                            context,
                            "mail.listing.compact.even_selected",
                        ),
                        even_highlighted: crate::conf::value(
                            context,
                            "mail.listing.compact.even_highlighted",
                        ),
                        odd_unseen: crate::conf::value(context, "mail.listing.compact.odd_unseen"),
                        odd_selected: crate::conf::value(
                            context,
                            "mail.listing.compact.odd_selected",
                        ),
                        odd_highlighted: crate::conf::value(
                            context,
                            "mail.listing.compact.odd_highlighted",
                        ),
                        even: crate::conf::value(context, "mail.listing.compact.even"),
                        odd: crate::conf::value(context, "mail.listing.compact.odd"),
                        attachment_flag: crate::conf::value(
                            context,
                            "mail.listing.attachment_flag",
                        ),
                        thread_snooze_flag: crate::conf::value(
                            context,
                            "mail.listing.thread_snooze_flag",
                        ),
                        tag_default: crate::conf::value(context, "mail.listing.tag_default"),
                        theme_default: crate::conf::value(context, "theme_default"),
                        ..self.color_cache
                    };
                    if !context.settings.terminal.use_color() {
                        self.color_cache.highlighted.attrs |= Attr::REVERSE;
                        self.color_cache.tag_default.attrs |= Attr::REVERSE;
                        self.color_cache.even_highlighted.attrs |= Attr::REVERSE;
                        self.color_cache.odd_highlighted.attrs |= Attr::REVERSE;
                    }
                }
                IndexStyle::Plain => todo!(),
                IndexStyle::Threaded => todo!(),
            }
        }

        pub fn unfocused(&self) -> bool {
            false
        }

        pub fn set_offline(&mut self, context: &mut Context) {
            let message = if let Err(err) = context.is_online(self.coordinates.0) {
                err.to_string()
            } else {
                use crate::conf::accounts::MailboxStatus;
                let account = &context.accounts[&self.coordinates.0];
                match account[&self.coordinates.1].status {
                    MailboxStatus::Available
                    | MailboxStatus::Parsing(_, _)
                    | MailboxStatus::None => "offline".to_string(),
                    MailboxStatus::Failed(ref err) => err.to_string(),
                }
            };
            self.data_columns.columns[0] =
                CellBuffer::new_with_context(message.len(), 1, None, context);
            write_string_to_grid(
                &message,
                &mut self.data_columns.columns[0],
                self.color_cache.theme_default.fg,
                self.color_cache.theme_default.bg,
                self.color_cache.theme_default.attrs,
                ((0, 0), (message.len() - 1, 0)),
                None,
            );
            self.state.job_state = JobState::Offline { message };
        }

        pub fn set_coordinates(
            &mut self,
            new_val: (AccountHash, MailboxHash),
            context: &mut Context,
        ) {
            if self.coordinates == new_val {
                return;
            }
            let mut is_cached_state = false;
            let new_state = if let Some(cached_state) = self.cached_states.remove(&new_val) {
                is_cached_state = true;
                cached_state
            } else {
                MailboxListState::new()
            };
            self.cached_states.insert(
                self.coordinates,
                std::mem::replace(&mut self.state, new_state),
            );
            self.coordinates = new_val;
            if context.is_online(self.coordinates.0).is_err() {
                self.set_offline(context);
            } else {
                use crate::conf::accounts::MailboxStatus;
                let account = &context.accounts[&self.coordinates.0];
                if account.mailbox_entries.contains_key(&self.coordinates.1) {
                    self.update_job_state(
                        &mut UIEvent::AccountStatusChange(self.coordinates.0),
                        context,
                    );
                    if is_cached_state {
                        self.initialize_rows(context);
                    } else {
                        let style =
                            *mailbox_settings!(context[new_val.0][&new_val.1].listing.index_style);
                        self.set_style(style, context);
                    }
                }
            }
        }

        pub fn set_movement(&mut self, mvm: PageMovement) {
            self.state.movement = Some(mvm);
            self.set_dirty(true);
        }

        pub fn selection(&mut self) -> &mut HashMap<ThreadHash, bool> {
            todo!()
        }
        pub fn perform_action(
            &mut self,
            context: &mut Context,
            thread_hashes: SmallVec<[ThreadHash; 8]>,
            a: &ListingAction,
        ) {
            todo!()
        }
        pub fn refresh_mailbox(&self, _context: &Context, force: bool) {
            todo!()
        }
        pub fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]> {
            todo!()
        }
        pub fn set_modifier_active(&mut self, _new_val: bool) {}
        pub fn set_modifier_command(&mut self, _new_val: Option<Modifier>) {}
        pub fn modifier_command(&self) -> Option<Modifier> {
            None
        }

        fn update_job_state(&mut self, event: &mut UIEvent, context: &mut Context) {
            debug!("update_job_state {:?}", &self.state.job_state);
            if let Err(err) = context.is_online(self.coordinates.0) {
                self.set_offline(context);
                return;
            } else {
                use crate::conf::accounts::MailboxStatus;
                match context.accounts[&self.coordinates.0].load2(self.coordinates.1) {
                    Ok(Some(job_id)) => {
                        self.state.job_state = JobState::Loading { job_id };
                        return;
                    }
                    Ok(None) => match self.state.job_state {
                        JobState::Offline { .. } | JobState::Loading { .. } => {
                            self.state.job_state = JobState::Loaded;
                            self.initialize_rows(context);
                            return;
                        }
                        _ => {}
                    },
                    Err(err) => {
                        self.state.job_state = JobState::Offline {
                            message: err.to_string(),
                        };
                        return;
                    }
                }
            }
            match (&self.state.job_state, &event) {
                (_, UIEvent::StatusEvent(StatusEvent::JobFinished(ref finished_job_id)))
                    if self
                        .state
                        .fetching_envelopes
                        .keys()
                        .any(|j| *j == *finished_job_id) =>
                {
                    if let Some(batch) = self.state.fetching_envelopes.remove(finished_job_id) {
                        debug!("fetching_envelopes done {:?}", &batch);
                        for env_hash in batch.iter() {
                            let _row = self.unloaded_envelope_hashes.remove(&env_hash);
                        }
                        self.force_draw = true;
                        self.set_dirty(true);
                    }
                }
                (
                    JobState::Filtering {
                        ref join_handle,
                        query: _,
                    },
                    UIEvent::StatusEvent(StatusEvent::JobFinished(ref finished_job_id)),
                ) if join_handle.job_id == *finished_job_id => {}
                (
                    JobState::Searching {
                        ref join_handle,
                        query: _,
                    },
                    UIEvent::StatusEvent(StatusEvent::JobFinished(ref finished_job_id)),
                ) if join_handle.job_id == *finished_job_id => {}
                _ => {}
            }
        }

        fn initialize_columns(&mut self) {
            self.rows_drawn = SegmentTree::from(
                std::iter::repeat(1)
                    .take(self.length)
                    .collect::<SmallVec<_>>(),
            );
            match self.state.style {
                IndexStyle::Plain | IndexStyle::Threaded | IndexStyle::Compact => {
                    self.data_columns.columns[0].resize(8, self.length, None);
                    self.data_columns.columns[0].clear(None);
                    self.data_columns.segment_tree[0] = SegmentTree::from(
                        std::iter::repeat(1)
                            .take(self.length)
                            .collect::<SmallVec<_>>(),
                    );
                    self.data_columns.columns[1].resize(32, self.length, None);
                    self.data_columns.columns[1].clear(None);
                    self.data_columns.segment_tree[1] = SegmentTree::from(
                        std::iter::repeat(0)
                            .take(self.length)
                            .collect::<SmallVec<_>>(),
                    );
                    self.data_columns.columns[2].resize(64, self.length, None);
                    self.data_columns.columns[2].clear(None);
                    self.data_columns.segment_tree[2] = SegmentTree::from(
                        std::iter::repeat(0)
                            .take(self.length)
                            .collect::<SmallVec<_>>(),
                    );
                    self.data_columns.columns[3].resize(16, self.length, None);
                    self.data_columns.columns[3].clear(None);
                    self.data_columns.segment_tree[3] = SegmentTree::from(
                        std::iter::repeat(0)
                            .take(self.length)
                            .collect::<SmallVec<_>>(),
                    );
                    self.data_columns.columns[4].resize(128, self.length, None);
                    self.data_columns.columns[4].clear(None);
                    self.data_columns.segment_tree[4] = SegmentTree::from(
                        std::iter::repeat(LOADING_STR.len().try_into().unwrap_or(0))
                            .take(self.length)
                            .collect::<SmallVec<_>>(),
                    );
                }
                IndexStyle::Conversations => {
                    self.data_columns.columns[0].resize(516, 2 * self.length, None);
                    self.data_columns.columns[0].clear(None);
                }
            }
            self.force_draw = true;
            self.set_dirty(true);
        }

        fn initialize_rows(&mut self, context: &mut Context) {
            let length: usize = match self.state.style {
                IndexStyle::Plain | IndexStyle::Threaded => todo!(),
                IndexStyle::Conversations | IndexStyle::Compact => context.accounts
                    [&self.coordinates.0]
                    .collection
                    .get_threads(self.coordinates.1)
                    .roots()
                    .len(),
            };
            self.length = length;

            self.rows.clear();
            self.entries.clear();
            self.unloaded_envelope_hashes.clear();
            self.initialize_columns();

            match self.state.style {
                IndexStyle::Plain | IndexStyle::Threaded => todo!(),
                IndexStyle::Conversations | IndexStyle::Compact => {
                    let threads = context.accounts[&self.coordinates.0]
                        .collection
                        .get_threads(self.coordinates.1);
                    let mut roots = threads.roots();
                    let envelopes = &context.accounts[&self.coordinates.0].collection.envelopes;
                    threads.group_inner_sort_by(&mut roots, self.state.sort, &envelopes);
                    self.rows.extend(
                        roots.into_iter().map(|thread_hash| {
                            (thread_hash, threads.thread_ref(thread_hash).root())
                        }),
                    );
                }
            }
            self.draw_rows(
                context,
                self.state.cursor_pos % self.last_recorded_page_size,
                std::cmp::min(
                    self.state.cursor_pos % self.last_recorded_page_size
                        + self.last_recorded_page_size,
                    self.length.saturating_sub(1),
                ),
            );
        }

        fn make_entry_string(
            coordinates: (AccountHash, MailboxHash),
            e: &Envelope,
            from: &[Address],
            thread_hash: ThreadHash,
            threads: &Threads,
            context: &Context,
        ) -> EntryStrings {
            let thread = threads.thread_ref(thread_hash);
            let mut tags = String::new();
            let mut colors = SmallVec::new();
            let account = &context.accounts[&coordinates.0];
            if account.backend_capabilities.supports_tags {
                let tags_lck = account.collection.tag_index.read().unwrap();
                for t in e.labels().iter() {
                    if mailbox_settings!(context[coordinates.0][&coordinates.1].tags.ignore_tags)
                        .contains(t)
                        || account_settings!(context[coordinates.0].tags.ignore_tags).contains(t)
                        || context.settings.tags.ignore_tags.contains(t)
                        || !tags_lck.contains_key(t)
                    {
                        continue;
                    }
                    tags.push(' ');
                    tags.push_str(tags_lck.get(t).as_ref().unwrap());
                    tags.push(' ');
                    colors.push(
                        mailbox_settings!(context[coordinates.0][&coordinates.1].tags.colors)
                            .get(t)
                            .cloned()
                            .or_else(|| {
                                account_settings!(context[coordinates.0].tags.colors)
                                    .get(t)
                                    .cloned()
                                    .or_else(|| context.settings.tags.colors.get(t).cloned())
                            }),
                    );
                }
                if !tags.is_empty() {
                    tags.pop();
                }
            }
            let mut subject = e.subject().to_string();
            subject.truncate_at_boundary(150);
            if thread.len() > 1 {
                subject = format!("{} ({})", subject, thread.len())
            };
            EntryStrings {
                date: DateString(format_date(context, thread.date())),
                subject: SubjectString(subject),
                flag: FlagString(format!(
                    "{}{}",
                    if thread.has_attachments() { "📎" } else { "" },
                    if thread.snoozed() { "💤" } else { "" }
                )),
                from: FromString(address_list!((from) as comma_sep_list)),
                tags: TagString(tags, colors),
            }
        }

        fn make_entry(
            coordinates: (AccountHash, MailboxHash),
            index: usize,
            thread_hash: ThreadHash,
            thread_node_hash: ThreadNodeHash,
            root_envelope_hash: EnvelopeHash,
            from_address_list: &mut Vec<Address>,
            from_address_set: &mut HashSet<Vec<u8>>,
            threads: &Threads,
            context: &Context,
        ) -> std::result::Result<Entry, (Entry, SmallVec<[EnvelopeHash; 16]>)> {
            let thread = threads.thread_ref(thread_hash);
            let unseen = thread.unseen() > 0;
            let has_attachments = thread.has_attachments();
            let snoozed = thread.snoozed();
            let mut envelopes_to_load = SmallVec::new();
            if !context.accounts[&coordinates.0].contains_key(root_envelope_hash) {
                envelopes_to_load.push(root_envelope_hash);
            }
            from_address_list.clear();
            from_address_set.clear();
            for env_hash in threads
                .thread_group_iter(thread_hash)
                .filter_map(|(_, h)| threads.thread_nodes()[&h].message())
            {
                if !context.accounts[&coordinates.0].contains_key(env_hash) {
                    envelopes_to_load.push(env_hash);
                    continue;
                }
                let envelope = context.accounts[&coordinates.0]
                    .collection
                    .get_env(env_hash);
                for addr in envelope.from().iter() {
                    if from_address_set.contains(addr.address_spec_raw()) {
                        continue;
                    }
                    from_address_set.insert(addr.address_spec_raw().to_vec());
                    from_address_list.push(addr.clone());
                }
            }
            if !envelopes_to_load.is_empty() {
                return Err((
                    Entry {
                        index,
                        root_envelope_hash,
                        thread_node_hash,
                        thread_hash,
                        unseen,
                        snoozed,
                        has_attachments,
                        strings: None,
                    },
                    envelopes_to_load,
                ));
            }
            let root_envelope: &EnvelopeRef = &context.accounts[&coordinates.0]
                .collection
                .get_env(root_envelope_hash);

            let strings = Some(Self::make_entry_string(
                coordinates,
                &root_envelope,
                &from_address_list,
                thread_hash,
                &threads,
                context,
            ));
            Ok(Entry {
                index,
                root_envelope_hash,
                thread_node_hash,
                thread_hash,
                unseen,
                snoozed,
                has_attachments,
                strings,
            })
        }

        fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
            debug!("draw_list {:?}", &self.state.job_state);
            debug!(self.length);
            debug!(self.force_draw);
            debug!(self.state.style);
            let (upper_left, bottom_right) = area;
            if self.length == 0 {
                clear_area(grid, area, self.color_cache.theme_default);
                copy_area(
                    grid,
                    &self.data_columns.columns[0],
                    area,
                    ((0, 0), pos_dec(self.data_columns.columns[0].size(), (1, 1))),
                );
                context.dirty_areas.push_back(area);
                return;
            }
            let rows: usize = match self.state.style {
                IndexStyle::Plain | IndexStyle::Threaded | IndexStyle::Compact => {
                    get_y(bottom_right) - get_y(upper_left) + 1
                }
                IndexStyle::Conversations => (get_y(bottom_right) - get_y(upper_left) + 1) / 2,
            };
            self.last_recorded_page_size = rows;
            if rows == 0 {
                return;
            }

            if let Some(mvm) = self.state.movement.take() {
                match mvm {
                    PageMovement::Up(amount) => {
                        self.state.new_cursor_pos =
                            self.state.new_cursor_pos.saturating_sub(amount);
                    }
                    PageMovement::PageUp(multiplier) => {
                        self.state.new_cursor_pos =
                            self.state.new_cursor_pos.saturating_sub(rows * multiplier);
                    }
                    PageMovement::Down(amount) => {
                        if self.state.new_cursor_pos + amount + 1 < self.length {
                            self.state.new_cursor_pos += amount;
                        } else {
                            self.state.new_cursor_pos = self.length - 1;
                        }
                    }
                    PageMovement::PageDown(multiplier) => {
                        if self.state.new_cursor_pos + rows * multiplier + 1 < self.length {
                            self.state.new_cursor_pos += rows * multiplier;
                        } else if self.state.new_cursor_pos + rows * multiplier > self.length {
                            self.state.new_cursor_pos = self.length - 1;
                        } else {
                            self.state.new_cursor_pos =
                                (self.length.saturating_sub(1) / rows) * rows;
                        }
                    }
                    PageMovement::Right(_) | PageMovement::Left(_) => {}
                    PageMovement::Home => {
                        self.state.new_cursor_pos = 0;
                    }
                    PageMovement::End => {
                        self.state.new_cursor_pos = self.length.saturating_sub(1);
                    }
                }
            }

            let prev_page_no = (self.state.cursor_pos).wrapping_div(rows);
            let page_no = (self.state.new_cursor_pos).wrapping_div(rows);

            let top_idx = page_no * rows;
            self.draw_rows(
                context,
                top_idx,
                std::cmp::min(top_idx + rows, self.length.saturating_sub(1)),
            );

            /* If cursor position has changed, remove the highlight from the previous position and
             * apply it in the new one. */
            if self.state.cursor_pos != self.state.new_cursor_pos
                && prev_page_no == page_no
                && !self.force_draw
            {
                let old_cursor_pos = self.state.cursor_pos;
                self.state.cursor_pos = self.state.new_cursor_pos;
                for &idx in &[old_cursor_pos, self.state.new_cursor_pos] {
                    if idx >= self.length {
                        continue;
                    }
                    let new_area = match self.state.style {
                        IndexStyle::Plain | IndexStyle::Threaded | IndexStyle::Compact => (
                            set_y(upper_left, get_y(upper_left) + (idx % rows)),
                            set_y(bottom_right, get_y(upper_left) + (idx % rows)),
                        ),
                        IndexStyle::Conversations => (
                            set_y(upper_left, get_y(upper_left) + 2 * (idx % rows)),
                            set_y(bottom_right, get_y(upper_left) + 2 * (idx % rows) + 1),
                        ),
                    };
                    self.highlight_line(grid, new_area, idx, context);
                    context.dirty_areas.push_back(new_area);
                }
                return;
            } else if self.state.cursor_pos != self.state.new_cursor_pos {
                self.state.cursor_pos = self.state.new_cursor_pos;
            } else if self.state.cursor_pos == self.state.new_cursor_pos
                && prev_page_no == page_no
                && !self.force_draw
            {
                return;
            }
            if self.state.new_cursor_pos >= self.length {
                self.state.new_cursor_pos = self.length - 1;
                self.state.cursor_pos = self.state.new_cursor_pos;
            }
            self.force_draw = false;

            clear_area(grid, area, self.color_cache.theme_default);
            /* Page_no has changed, so draw new page */
            match self.state.style {
                IndexStyle::Conversations => {
                    copy_area(
                        grid,
                        &self.data_columns.columns[0],
                        (
                            upper_left,
                            set_x(
                                bottom_right,
                                std::cmp::min(
                                    get_x(bottom_right),
                                    get_x(upper_left) + self.data_columns.columns[0].size().0,
                                ),
                            ),
                        ),
                        (
                            (0, 2 * top_idx),
                            pos_dec(self.data_columns.columns[0].size(), (1, 1)),
                        ),
                    );

                    self.highlight_line(
                        grid,
                        (
                            pos_inc(upper_left, (0, 2 * (self.state.cursor_pos % rows))),
                            set_y(
                                bottom_right,
                                get_y(upper_left) + 2 * (self.state.cursor_pos % rows) + 1,
                            ),
                        ),
                        self.state.cursor_pos,
                        context,
                    );

                    /* calculate how many entries are visible in this page */
                    let rows = if top_idx + rows > self.length {
                        clear_area(
                            grid,
                            (
                                pos_inc(upper_left, (0, 2 * (self.length - top_idx))),
                                bottom_right,
                            ),
                            self.color_cache.theme_default,
                        );
                        self.length - top_idx
                    } else {
                        rows
                    };

                    /* fill any remaining columns, if our view is wider than
                     * self.data_columns.columns[0] */
                    let width = self.data_columns.columns[0].size().0;
                    let padding_fg = self.color_cache.padding.fg;

                    if width < width!(area) {
                        let y_offset = get_y(upper_left);
                        for y in 0..rows {
                            let bg_color =
                                grid[(get_x(upper_left) + width - 1, y_offset + 2 * y)].bg();
                            for x in (get_x(upper_left) + width)..=get_x(bottom_right) {
                                grid[(x, y_offset + 2 * y)].set_bg(bg_color);
                                grid[(x, y_offset + 2 * y + 1)]
                                    .set_ch('▁')
                                    .set_fg(self.color_cache.theme_default.fg)
                                    .set_bg(bg_color);
                            }
                        }
                    }
                }
                other => {
                    let width = width!(area);

                    for &i in &[0, 1, 2, 3, 4] {
                        /* Set column widths to their maximum value width in the
                         * range [top_idx, top_idx + rows]. By using a segment
                         * tree the query is O(logn), which is great!
                         */
                        self.data_columns.widths[i] = self.data_columns.segment_tree[i]
                            .get_max(top_idx, top_idx + rows)
                            as usize;
                    }
                    if self.data_columns.widths.iter().sum::<usize>() > width {
                        let diff = self.data_columns.widths.iter().sum::<usize>() - width;
                        if self.data_columns.widths[2] > 2 * diff {
                            self.data_columns.widths[2] -= diff;
                        } else {
                            self.data_columns.widths[2] = std::cmp::min(
                                self.data_columns.widths[2],
                                std::cmp::max(
                                    15,
                                    self.data_columns.widths[2].saturating_sub((2 * diff) / 3),
                                ),
                            );
                            self.data_columns.widths[4] = std::cmp::min(
                                self.data_columns.widths[4],
                                std::cmp::max(
                                    15,
                                    self.data_columns.widths[4].saturating_sub(diff / 3 + diff % 3),
                                ),
                            );
                        }
                    }
                    let mut x = get_x(upper_left);
                    let mut flag_x = 0;
                    for i in 0..5 {
                        let column_width = self.data_columns.columns[i].size().0;
                        if i == 3 {
                            flag_x = x;
                        }
                        if self.data_columns.widths[i] == 0 {
                            continue;
                        }
                        copy_area(
                            grid,
                            &self.data_columns.columns[i],
                            (
                                set_x(upper_left, x),
                                set_x(
                                    bottom_right,
                                    std::cmp::min(
                                        get_x(bottom_right),
                                        x + (self.data_columns.widths[i]),
                                    ),
                                ),
                            ),
                            (
                                (0, top_idx),
                                (column_width.saturating_sub(1), self.length - 1),
                            ),
                        );
                        for (r, row) in grid
                            .bounds_iter((
                                set_x(
                                    upper_left,
                                    std::cmp::min(
                                        get_x(bottom_right),
                                        x + (self.data_columns.widths[i]),
                                    ),
                                ),
                                (set_x(
                                    bottom_right,
                                    std::cmp::min(
                                        get_x(bottom_right),
                                        x + (self.data_columns.widths[i]) + 2,
                                    ),
                                )),
                            ))
                            .enumerate()
                        {
                            let bg_color = grid[pos_inc(upper_left, (0, r))].bg();
                            for c in row {
                                grid[c].set_bg(bg_color);
                            }
                        }
                        x += self.data_columns.widths[i] + 2; // + SEPARATOR
                        if x > get_x(bottom_right) {
                            break;
                        }
                    }

                    self.highlight_line(
                        grid,
                        (
                            set_y(
                                upper_left,
                                get_y(upper_left) + (self.state.cursor_pos % rows),
                            ),
                            set_y(
                                bottom_right,
                                get_y(upper_left) + (self.state.cursor_pos % rows),
                            ),
                        ),
                        self.state.cursor_pos,
                        context,
                    );

                    if top_idx + rows > self.length {
                        clear_area(
                            grid,
                            (
                                pos_inc(upper_left, (0, self.length - top_idx)),
                                bottom_right,
                            ),
                            self.color_cache.theme_default,
                        );
                    }
                }
            }

            context.dirty_areas.push_back(area);
        }

        fn draw_rows(&mut self, context: &mut Context, start: usize, end: usize) {
            if self.length == 0 {
                return;
            }
            debug_assert!(end >= start);
            if self.rows_drawn.get_max(start, end) == 0 {
                debug!("not drawing {}-{}", start, end);
                return;
            }
            debug!("drawing {}-{}", start, end);
            let mut envelopes_to_load: Vec<EnvelopeHash> = vec![];
            match self.state.style {
                IndexStyle::Plain | IndexStyle::Threaded => todo!(),
                IndexStyle::Conversations | IndexStyle::Compact => {
                    let threads = context.accounts[&self.coordinates.0]
                        .collection
                        .get_threads(self.coordinates.1);
                    let mut from_address_list = Vec::new();
                    let mut from_address_set: HashSet<Vec<u8>> = HashSet::new();
                    let mut stack: std::collections::VecDeque<ThreadNodeHash> =
                        std::collections::VecDeque::new();
                    for (i, (thread_hash, thread_node_hash, root_envelope_hash)) in self
                        .rows
                        .iter()
                        .enumerate()
                        .skip(start)
                        .take(end - start + 1)
                        .filter(|(i, _)| self.rows_drawn.get(*i) != 0)
                        .filter_map(|(i, &(thread_hash, thread_node_hash))| {
                            let thread_node = &threads.thread_nodes()[&thread_node_hash];
                            thread_node
                                .message()
                                .or_else(|| {
                                    stack.clear();
                                    stack.extend(thread_node.children().iter());
                                    let mut iter_ptr = thread_node_hash;
                                    while let Some(iter) = stack.pop_front() {
                                        if threads.thread_nodes()[&iter].message().is_some() {
                                            iter_ptr = iter;
                                            break;
                                        }
                                        stack.extend(
                                            threads.thread_nodes()[&iter].children().iter(),
                                        );
                                    }
                                    threads.thread_nodes()[&iter_ptr].message()
                                })
                                .map(|h| (i, (thread_hash, thread_node_hash, h)))
                        })
                        .collect::<SmallVec<[_; 1024]>>()
                    {
                        match Self::make_entry(
                            self.coordinates,
                            i,
                            thread_hash,
                            thread_node_hash,
                            root_envelope_hash,
                            &mut from_address_list,
                            &mut from_address_set,
                            &threads,
                            context,
                        ) {
                            Ok(entry) => {
                                self.entries.insert(i, entry);
                                self.force_draw = true;
                                self.rows_drawn.update(i, 0);
                            }
                            Err((entry, batch)) => {
                                self.entries.insert(i, entry);
                                for &env_hash in &batch {
                                    self.unloaded_envelope_hashes.insert(env_hash, i);
                                }
                                envelopes_to_load.extend(batch.into_iter());
                            }
                        }
                    }
                }
            };
            if let Ok(batch) = EnvelopeHashBatch::try_from(envelopes_to_load.as_slice()) {
                match context.accounts[&self.coordinates.0].fetch_batch(batch.clone()) {
                    Err(err) => {}
                    Ok(job_id) => {
                        self.state.fetching_envelopes.insert(job_id, batch);
                    }
                }
            }

            match self.state.style {
                IndexStyle::Conversations => {
                    let (width, _) = self.data_columns.columns[0].size();
                    for i in start..=end {
                        if let Some(Entry {
                            strings: Some(ref strings),
                            unseen,
                            ..
                        }) = self.entries.get(&i)
                        {
                            conversations::draw_entry(
                                &mut self.data_columns.columns[0],
                                strings,
                                ((0, 2 * i), (width - 1, 2 * i + 1)),
                                &self.color_cache,
                                *unseen,
                                false,
                                false,
                            );
                        } else {
                            write_string_to_grid(
                                LOADING_STR,
                                &mut self.data_columns.columns[0],
                                self.color_cache.theme_default.fg,
                                self.color_cache.theme_default.bg,
                                self.color_cache.theme_default.attrs,
                                ((0, 2 * i), (width - 1, 2 * i)),
                                None,
                            );
                            for x in 0..width {
                                self.data_columns.columns[0][(x, 2 * i + 1)]
                                    .set_ch('▁')
                                    .set_fg(self.color_cache.theme_default.fg)
                                    .set_bg(self.color_cache.theme_default.bg);
                            }
                        }
                    }
                }
                IndexStyle::Plain | IndexStyle::Threaded | IndexStyle::Compact => {
                    let Self {
                        ref entries,
                        ref mut data_columns,
                        ref color_cache,
                        ..
                    } = self;
                    for (i, strings) in (start..=end).into_iter().filter_map(|i| {
                        entries
                            .get(&i)
                            .and_then(|entry| entry.strings.as_ref().map(|s| (i, s)))
                    }) {
                        data_columns.segment_tree[0]
                            .update(i, i.to_string().grapheme_width().try_into().unwrap_or(255));
                        /* date */
                        data_columns.segment_tree[1]
                            .update(i, strings.date.grapheme_width().try_into().unwrap_or(255));
                        /* from */
                        data_columns.segment_tree[2]
                            .update(i, strings.from.grapheme_width().try_into().unwrap_or(255));
                        /* flags */
                        data_columns.segment_tree[3]
                            .update(i, strings.flag.grapheme_width().try_into().unwrap_or(255));
                        /* subject */
                        data_columns.segment_tree[4].update(
                            i,
                            (strings.subject.grapheme_width() + 2 + strings.tags.grapheme_width())
                                .try_into()
                                .unwrap_or(255),
                        );
                    }
                    for i in start..=end {
                        if let Some(Entry {
                            strings: Some(ref strings),
                            unseen,
                            snoozed,
                            has_attachments,
                            ..
                        }) = entries.get(&i)
                        {
                            single_row::draw_entry(
                                &mut data_columns.columns,
                                i,
                                strings,
                                *snoozed,
                                *has_attachments,
                                *unseen,
                                false,
                                false,
                                &color_cache,
                            );
                        } else {
                            single_row::draw_loading_entry(
                                &mut data_columns.columns,
                                i,
                                false,
                                &color_cache,
                            );
                        }
                    }
                }
            }
        }

        fn highlight_line(
            &mut self,
            grid: &mut CellBuffer,
            area: Area,
            idx: usize,
            context: &Context,
        ) {
            if self.length == 0 {
                return;
            }
            match self.state.style {
                IndexStyle::Plain | IndexStyle::Threaded | IndexStyle::Compact => {
                    self.highlight_single_line(grid, area, idx, context);
                }
                IndexStyle::Conversations => {
                    self.highlight_double_line(grid, area, idx, context);
                }
            }
        }

        fn get_thread_under_cursor(&self, cursor: usize) -> Option<ThreadHash> {
            self.rows.get(cursor).map(|&(t, _)| t)
        }
    }

    impl fmt::Display for MailboxList {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "mail")
        }
    }

    impl Component for MailboxList {
        fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
            if !self.is_dirty() {
                return;
            }
            if !is_valid_area!(area) {
                return;
            }
            let (upper_left, bottom_right) = area;
            {
                let mut area = if self.unfocused {
                    clear_area(
                        grid,
                        (
                            pos_inc(upper_left, (width!(area) / 3, 0)),
                            set_x(bottom_right, get_x(upper_left) + width!(area) / 3 + 1),
                        ),
                        self.color_cache.theme_default,
                    );
                    context.dirty_areas.push_back((
                        pos_inc(upper_left, (width!(area) / 3, 0)),
                        set_x(bottom_right, get_x(upper_left) + width!(area) / 3 + 1),
                    ));
                    (
                        upper_left,
                        set_x(bottom_right, get_x(upper_left) + width!(area) / 3 - 1),
                    )
                } else {
                    area
                };

                /*
                if !self.state.filter_term.is_empty() {
                    let (x, y) = write_string_to_grid(
                        &format!(
                            "{} results for `{}` (Press ESC to exit)",
                            self.filtered_selection.len(),
                            self.filter_term
                        ),
                        grid,
                        self.color_cache.theme_default.fg,
                        self.color_cache.theme_default.bg,
                        self.color_cache.theme_default.attrs,
                        area,
                        Some(get_x(upper_left)),
                    );
                    for c in grid.row_iter(x..(get_x(bottom_right) + 1), y) {
                        grid[c] = Cell::default();
                    }
                    clear_area(
                        grid,
                        ((x, y), set_y(bottom_right, y)),
                        self.color_cache.theme_default,
                    );
                    context
                        .dirty_areas
                        .push_back((upper_left, set_y(bottom_right, y + 1)));

                    area = (set_y(upper_left, y + 1), bottom_right);
                }
                */
                let (upper_left, bottom_right) = area;
                let rows: usize = match self.state.style {
                    IndexStyle::Plain | IndexStyle::Threaded | IndexStyle::Compact => {
                        get_y(bottom_right) - get_y(upper_left)
                    }
                    IndexStyle::Conversations => (get_y(bottom_right) - get_y(upper_left) + 1) / 2,
                };
                if let Some(modifier) = self.state.modifier_command.take() {
                    if let Some(mvm) = self.state.movement.as_ref() {
                        /*
                            match mvm {
                                PageMovement::Up(amount) => {
                                    for c in self.new_cursor_pos.saturating_sub(*amount)
                                        ..=self.new_cursor_pos
                                    {
                                        let thread = self.get_thread_under_cursor(c);
                                        match modifier {
                                            Modifier::SymmetricDifference => {
                                                self.selection.entry(thread).and_modify(|e| *e = !*e);
                                            }
                                            Modifier::Union => {
                                                self.selection.entry(thread).and_modify(|e| *e = true);
                                            }
                                            Modifier::Difference => {
                                                self.selection.entry(thread).and_modify(|e| *e = false);
                                            }
                                            Modifier::Intersection => {}
                                        }
                                        self.row_updates.push(thread);
                                    }
                                    if modifier == Modifier::Intersection {
                                        for c in (0..self.new_cursor_pos.saturating_sub(*amount))
                                            .chain((self.new_cursor_pos + 2)..self.length)
                                        {
                                            let thread = self.get_thread_under_cursor(c);
                                            self.selection.entry(thread).and_modify(|e| *e = false);
                                            self.row_updates.push(thread);
                                        }
                                    }
                                }
                                PageMovement::PageUp(multiplier) => {
                                    for c in self.new_cursor_pos.saturating_sub(rows * multiplier)
                                        ..=self.new_cursor_pos
                                    {
                                        let thread = self.get_thread_under_cursor(c);
                                        match modifier {
                                            Modifier::SymmetricDifference => {
                                                self.selection.entry(thread).and_modify(|e| *e = !*e);
                                            }
                                            Modifier::Union => {
                                                self.selection.entry(thread).and_modify(|e| *e = true);
                                            }
                                            Modifier::Difference => {
                                                self.selection.entry(thread).and_modify(|e| *e = false);
                                            }
                                            Modifier::Intersection => {}
                                        }
                                        self.row_updates.push(thread);
                                    }
                                }
                                PageMovement::Down(amount) => {
                                    for c in self.new_cursor_pos
                                        ..std::cmp::min(self.length, self.new_cursor_pos + amount + 1)
                                    {
                                        let thread = self.get_thread_under_cursor(c);
                                        match modifier {
                                            Modifier::SymmetricDifference => {
                                                self.selection.entry(thread).and_modify(|e| *e = !*e);
                                            }
                                            Modifier::Union => {
                                                self.selection.entry(thread).and_modify(|e| *e = true);
                                            }
                                            Modifier::Difference => {
                                                self.selection.entry(thread).and_modify(|e| *e = false);
                                            }
                                            Modifier::Intersection => {}
                                        }
                                        self.row_updates.push(thread);
                                    }
                                    if modifier == Modifier::Intersection {
                                        for c in (0..self.new_cursor_pos).chain(
                                            (std::cmp::min(
                                                self.length,
                                                self.new_cursor_pos + amount + 1,
                                            ) + 1)..self.length,
                                        ) {
                                            let thread = self.get_thread_under_cursor(c);
                                            self.selection.entry(thread).and_modify(|e| *e = false);
                                            self.row_updates.push(thread);
                                        }
                                    }
                                }
                                PageMovement::PageDown(multiplier) => {
                                    for c in self.new_cursor_pos
                                        ..std::cmp::min(
                                            self.new_cursor_pos + rows * multiplier + 1,
                                            self.length,
                                        )
                                    {
                                        let thread = self.get_thread_under_cursor(c);
                                        match modifier {
                                            Modifier::SymmetricDifference => {
                                                self.selection.entry(thread).and_modify(|e| *e = !*e);
                                            }
                                            Modifier::Union => {
                                                self.selection.entry(thread).and_modify(|e| *e = true);
                                            }
                                            Modifier::Difference => {
                                                self.selection.entry(thread).and_modify(|e| *e = false);
                                            }
                                            Modifier::Intersection => {}
                                        }
                                        self.row_updates.push(thread);
                                    }
                                    if modifier == Modifier::Intersection {
                                        for c in (0..self.new_cursor_pos).chain(
                                            (std::cmp::min(
                                                self.new_cursor_pos + rows * multiplier + 1,
                                                self.length,
                                            ) + 1)..self.length,
                                        ) {
                                            let thread = self.get_thread_under_cursor(c);
                                            self.selection.entry(thread).and_modify(|e| *e = false);
                                            self.row_updates.push(thread);
                                        }
                                    }
                                }
                                PageMovement::Right(_) | PageMovement::Left(_) => {}
                                PageMovement::Home => {
                                    for c in 0..=self.new_cursor_pos {
                                        let thread = self.get_thread_under_cursor(c);
                                        match modifier {
                                            Modifier::SymmetricDifference => {
                                                self.selection.entry(thread).and_modify(|e| *e = !*e);
                                            }
                                            Modifier::Union => {
                                                self.selection.entry(thread).and_modify(|e| *e = true);
                                            }
                                            Modifier::Difference => {
                                                self.selection.entry(thread).and_modify(|e| *e = false);
                                            }
                                            Modifier::Intersection => {}
                                        }
                                        self.row_updates.push(thread);
                                    }
                                    if modifier == Modifier::Intersection {
                                        for c in (self.new_cursor_pos + 1)..self.length {
                                            let thread = self.get_thread_under_cursor(c);
                                            self.selection.entry(thread).and_modify(|e| *e = false);
                                            self.row_updates.push(thread);
                                        }
                                    }
                                }
                                PageMovement::End => {
                                    for c in self.new_cursor_pos..self.length {
                                        let thread = self.get_thread_under_cursor(c);
                                        match modifier {
                                            Modifier::SymmetricDifference => {
                                                self.selection.entry(thread).and_modify(|e| *e = !*e);
                                            }
                                            Modifier::Union => {
                                                self.selection.entry(thread).and_modify(|e| *e = true);
                                            }
                                            Modifier::Difference => {
                                                self.selection.entry(thread).and_modify(|e| *e = false);
                                            }
                                            Modifier::Intersection => {}
                                        }
                                        self.row_updates.push(thread);
                                    }
                                    if modifier == Modifier::Intersection {
                                        for c in 0..self.new_cursor_pos {
                                            let thread = self.get_thread_under_cursor(c);
                                            self.selection.entry(thread).and_modify(|e| *e = false);
                                            self.row_updates.push(thread);
                                        }
                                    }
                                }
                            }
                        */
                    }
                }
                /*
                        if !self.row_updates.is_empty() {
                            /* certain rows need to be updated (eg an unseen message was just set seen)
                             * */
                            while let Some(row) = self.row_updates.pop() {
                                self.update_line(context, row);
                                let row: usize = self.order[&row];

                                let page_no = (self.cursor_pos).wrapping_div(rows);

                                let top_idx = page_no * rows;
                                /* Update row only if it's currently visible */
                                if row >= top_idx && row < top_idx + rows {
                                    let area = (
                                        set_y(upper_left, get_y(upper_left) + (3 * (row % rows))),
                                        set_y(bottom_right, get_y(upper_left) + (3 * (row % rows) + 2)),
                                    );
                                    self.highlight_line(grid, area, row, context);
                                    context.dirty_areas.push_back(area);
                                }
                            }
                            if self.force_draw {
                                /* Draw the entire list */
                                self.draw_list(grid, area, context);
                                self.force_draw = false;
                            }
                        } else {
                            /* Draw the entire list */
                            self.draw_list(grid, area, context);
                        }
                */
                /* Draw the entire list */
                self.draw_list(grid, area, context);
            }
            if self.unfocused {
                if self.length == 0 && self.dirty {
                    clear_area(grid, area, self.color_cache.theme_default);
                    context.dirty_areas.push_back(area);
                    return;
                }

                let area = (
                    set_x(upper_left, get_x(upper_left) + width!(area) / 3 + 2),
                    bottom_right,
                );
                self.view.draw(grid, area, context);
            }
            self.dirty = false;
        }

        fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
            debug!(
                "process_event: {:?} state: {:?}",
                event, &self.state.job_state
            );
            match (&self.state.job_state, &event) {
                (JobState::Offline { .. }, UIEvent::AccountStatusChange(account_hash))
                    if self.coordinates.0 == *account_hash =>
                {
                    self.update_job_state(event, context);
                    self.set_dirty(true);
                }
                (
                    JobState::Loading { ref job_id },
                    UIEvent::StatusEvent(StatusEvent::JobCanceled(ref canceled_job_id)),
                ) if *job_id == *canceled_job_id => {
                    self.update_job_state(event, context);
                    self.set_dirty(true);
                    return false;
                }
                (
                    JobState::Loading { ref job_id },
                    UIEvent::StatusEvent(StatusEvent::JobFinished(ref finished_job_id)),
                ) if *job_id == *finished_job_id => {
                    self.update_job_state(event, context);
                    self.set_dirty(true);
                    return false;
                }
                (_, UIEvent::StatusEvent(StatusEvent::JobFinished(ref finished_job_id)))
                    if self
                        .state
                        .fetching_envelopes
                        .keys()
                        .any(|j| *j == *finished_job_id) =>
                {
                    self.update_job_state(event, context);
                    self.set_dirty(true);
                    return false;
                }
                (
                    JobState::Filtering {
                        ref join_handle,
                        query: _,
                    },
                    UIEvent::StatusEvent(StatusEvent::JobFinished(ref finished_job_id)),
                ) if join_handle.job_id == *finished_job_id => {
                    self.update_job_state(event, context);
                    self.set_dirty(true);
                    return false;
                }
                (
                    JobState::Searching {
                        ref join_handle,
                        query: _,
                    },
                    UIEvent::StatusEvent(StatusEvent::JobFinished(ref finished_job_id)),
                ) if join_handle.job_id == *finished_job_id => {
                    self.update_job_state(event, context);
                    self.set_dirty(true);
                    return false;
                }
                (_, _) => {}
            }
            if self.unfocused && self.view.process_event(event, context) {
                return true;
            }

            let shortcuts = self.get_shortcuts(context);
            if self.length > 0 {
                match *event {
                    UIEvent::Input(ref k)
                        if !self.unfocused
                            && shortcut!(
                                k == shortcuts[CompactListing::DESCRIPTION]["open_thread"]
                            ) =>
                    {
                        if let Some(thread_hash) =
                            self.get_thread_under_cursor(self.state.cursor_pos)
                        {
                            self.view = ThreadView::new(
                                (
                                    self.coordinates.0,
                                    self.coordinates.1,
                                    self.state.cursor_pos,
                                ),
                                thread_hash,
                                None,
                                context,
                            );
                            self.unfocused = true;
                            self.dirty = true;
                        }
                        return true;
                    }
                    UIEvent::Input(ref k)
                        if self.unfocused
                            && shortcut!(
                                k == shortcuts[CompactListing::DESCRIPTION]["exit_thread"]
                            ) =>
                    {
                        self.unfocused = false;
                        self.dirty = true;
                        /* If self.row_updates is not empty and we exit a thread, the row_update events
                         * will be performed but the list will not be drawn. So force a draw in any case.
                         * */
                        self.force_draw = true;
                        return true;
                    }
                    UIEvent::Input(ref key)
                        if !self.unfocused
                            && shortcut!(
                                key == shortcuts[Listing::DESCRIPTION]["select_entry"]
                            ) =>
                    {
                        if self.state.modifier_active && self.state.modifier_command.is_none() {
                            self.state.modifier_command = Some(Modifier::default());
                        } else {
                            let thread_hash = self.get_thread_under_cursor(self.state.cursor_pos);
                            //self.selection.entry(thread_hash).and_modify(|e| *e = !*e);
                            //self.row_updates.push(thread_hash);
                        }
                        return true;
                    }
                    UIEvent::Action(ref action) => {
                        match action {
                            Action::Sort(field, order) if !self.unfocused => {
                                debug!("Sort {:?} , {:?}", field, order);
                                self.state.sort = (*field, *order);
                                //if !self.filtered_selection.is_empty() {
                                //    // FIXME: perform sort
                                //    self.dirty = true;
                                //} else {
                                //    self.refresh_mailbox(context, false);
                                //}
                                return true;
                            }
                            Action::SubSort(field, order) if !self.unfocused => {
                                debug!("SubSort {:?} , {:?}", field, order);
                                self.state.subsort = (*field, *order);
                                // FIXME: perform subsort.
                                return true;
                            }
                            Action::Listing(ToggleThreadSnooze) if !self.unfocused => {
                                /*
                                let thread = self.get_thread_under_cursor(self.state.cursor_pos);
                                let account = &mut context.accounts[&self.coordinates.0];
                                account
                                    .collection
                                    .threads
                                    .write()
                                    .unwrap()
                                    .entry(self.coordinates.1)
                                    .and_modify(|threads| {
                                        let is_snoozed = threads.thread_ref(thread).snoozed();
                                        threads.thread_ref_mut(thread).set_snoozed(!is_snoozed);
                                    });
                                self.row_updates.push(thread);
                                self.refresh_mailbox(context, false);
                                */
                                return true;
                            }

                            _ => {}
                        }
                    }
                    _ => {}
                }
            }
            match *event {
                UIEvent::ConfigReload { old_settings: _ } => {
                    self.set_dirty(true);
                }
                UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
                    /*
                    let account = &context.accounts[&self.coordinates.0];
                    let threads = account.collection.get_threads(self.coordinates.1);
                    if !account.collection.contains_key(&new_hash) {
                        return false;
                    }
                    let new_env_thread_node_hash = account.collection.get_env(*new_hash).thread();
                    if !threads.thread_nodes.contains_key(&new_env_thread_node_hash) {
                        return false;
                    }
                    let thread: ThreadHash =
                        threads.find_group(threads.thread_nodes()[&new_env_thread_node_hash].group);
                    drop(threads);
                    if self.order.contains_key(&thread) {
                        self.row_updates.push(thread);
                    }

                    self.dirty = true;

                    if self.unfocused {
                        self.view
                            .process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
                    }
                    */
                }
                UIEvent::EnvelopeRemove(ref _env_hash, ref thread_hash) => {
                    /*
                    if self.order.contains_key(thread_hash) {
                        self.refresh_mailbox(context, false);
                        self.set_dirty(true);
                    }
                    */
                }
                UIEvent::EnvelopeUpdate(ref env_hash) => {
                    /*
                    let account = &context.accounts[&self.coordinates.0];
                    let threads = account.collection.get_threads(self.coordinates.1);
                    if !account.collection.contains_key(&env_hash) {
                        return false;
                    }
                    let new_env_thread_node_hash = account.collection.get_env(*env_hash).thread();
                    if !threads.thread_nodes.contains_key(&new_env_thread_node_hash) {
                        return false;
                    }
                    let thread: ThreadHash =
                        threads.find_group(threads.thread_nodes()[&new_env_thread_node_hash].group);
                    drop(threads);
                    if self.order.contains_key(&thread) {
                        self.row_updates.push(thread);
                    }

                    self.dirty = true;

                    if self.unfocused {
                        self.view
                            .process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
                    }
                    */
                }
                UIEvent::ChangeMode(UIMode::Normal) => {
                    self.dirty = true;
                }
                UIEvent::Resize => {
                    self.dirty = true;
                }
                /*
                UIEvent::Input(Key::Esc)
                    if !self.unfocused
                        && self.selection.values().cloned().any(std::convert::identity) =>
                {
                    for v in self.selection.values_mut() {
                        *v = false;
                    }
                    self.dirty = true;
                    return true;
                }
                UIEvent::Input(Key::Esc) if !self.unfocused && !self.filter_term.is_empty() => {
                    /*
                    self.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
                    self.refresh_mailbox(context, false);
                    self.set_dirty(true);
                    */
                    return true;
                }
                */
                UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused => {
                    /*
                    match context.accounts[&self.coordinates.0].search(
                        filter_term,
                        self.sort,
                        self.coordinates.1,
                    ) {
                        Ok(job) => {
                            let handle = context.accounts[&self.coordinates.0]
                                .job_executor
                                .spawn_specialized(job);
                            self.search_job = Some((filter_term.to_string(), handle));
                        }
                        Err(err) => {
                            context.replies.push_back(UIEvent::Notification(
                                    Some("Could not perform search".to_string()),
                                    err.to_string(),
                                    Some(crate::types::NotificationType::Error(err.kind)),
                            ));
                        }
                    };
                    self.set_dirty(true);
                    */
                }
                UIEvent::Action(Action::Listing(Select(ref search_term))) if !self.unfocused => {
                    /*
                    match context.accounts[&self.coordinates.0].search(
                        search_term,
                        self.sort,
                        self.coordinates.1,
                    ) {
                        Ok(job) => {
                            let mut handle = context.accounts[&self.coordinates.0]
                                .job_executor
                                .spawn_specialized(job);
                            if let Ok(Some(search_result)) = try_recv_timeout!(&mut handle.chan) {
                                self.select(search_term, search_result, context);
                            } else {
                                self.select_job = Some((search_term.to_string(), handle));
                            }
                        }
                        Err(err) => {
                            context.replies.push_back(UIEvent::Notification(
                                    Some("Could not perform search".to_string()),
                                    err.to_string(),
                                    Some(crate::types::NotificationType::Error(err.kind)),
                            ));
                        }
                    };
                    self.set_dirty(true);
                    */
                    return true;
                }
                /*
                UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id))
                    if self
                        .search_job
                            .as_ref()
                            .map(|(_, j)| j == job_id)
                            .unwrap_or(false) =>
                    {
                        let (filter_term, mut handle) = self.search_job.take().unwrap();
                        match handle.chan.try_recv() {
                            Err(_) => { /* search was canceled */ }
                            Ok(None) => { /* something happened, perhaps a worker thread panicked */ }
                            Ok(Some(results)) => self.filter(filter_term, results, context),
                        }
                        self.set_dirty(true);
                    }
                UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id))
                    if self
                        .select_job
                            .as_ref()
                            .map(|(_, j)| j == job_id)
                            .unwrap_or(false) =>
                    {
                        let (search_term, mut handle) = self.select_job.take().unwrap();
                        match handle.chan.try_recv() {
                            Err(_) => { /* search was canceled */ }
                            Ok(None) => { /* something happened, perhaps a worker thread panicked */ }
                            Ok(Some(results)) => self.select(&search_term, results, context),
                        }
                        self.set_dirty(true);
                    }
                */
                _ => {}
            }
            false
        }

        fn is_dirty(&self) -> bool {
            self.dirty
        }

        fn set_dirty(&mut self, value: bool) {
            self.dirty = value;
        }

        fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
            let mut map = if self.unfocused {
                self.view.get_shortcuts(context)
            } else {
                ShortcutMaps::default()
            };

            let config_map = context.settings.shortcuts.compact_listing.key_values();
            map.insert(CompactListing::DESCRIPTION, config_map);
            let config_map = context.settings.shortcuts.listing.key_values();
            map.insert(Listing::DESCRIPTION, config_map);

            map
        }

        fn id(&self) -> ComponentId {
            self.id
        }

        fn set_id(&mut self, id: ComponentId) {
            self.id = id;
        }
    }

    fn format_date(context: &Context, epoch: UnixTimestamp) -> String {
        let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(epoch);
        let now: std::time::Duration = std::time::SystemTime::now()
            .duration_since(d)
            .unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
        match now.as_secs() {
            n if context.settings.listing.recent_dates && n < 60 * 60 => format!(
                "{} minute{} ago",
                n / (60),
                if n / 60 == 1 { "" } else { "s" }
            ),
            n if context.settings.listing.recent_dates && n < 24 * 60 * 60 => format!(
                "{} hour{} ago",
                n / (60 * 60),
                if n / (60 * 60) == 1 { "" } else { "s" }
            ),
            n if context.settings.listing.recent_dates && n < 7 * 24 * 60 * 60 => format!(
                "{} day{} ago",
                n / (24 * 60 * 60),
                if n / (24 * 60 * 60) == 1 { "" } else { "s" }
            ),
            _ => melib::datetime::timestamp_to_string(
                epoch,
                context
                    .settings
                    .listing
                    .datetime_fmt
                    .as_ref()
                    .map(String::as_str)
                    .or(Some("%Y-%m-%d %T")),
                false,
            ),
        }
    }

    mod conversations {
        use super::*;
        macro_rules! row_attr {
            ($field:ident, $color_cache:expr, $unseen:expr, $highlighted:expr, $selected:expr  $(,)*) => {{
                ThemeAttribute {
                    fg: if $highlighted {
                        $color_cache.highlighted.fg
                    } else if $selected {
                        $color_cache.selected.fg
                    } else if $unseen {
                        $color_cache.unseen.fg
                    } else {
                        $color_cache.$field.fg
                    },
                    bg: if $highlighted {
                        $color_cache.highlighted.bg
                    } else if $selected {
                        $color_cache.selected.bg
                    } else if $unseen {
                        $color_cache.unseen.bg
                    } else {
                        $color_cache.$field.bg
                    },
                    attrs: if $highlighted {
                        $color_cache.highlighted.attrs
                    } else if $selected {
                        $color_cache.selected.attrs
                    } else if $unseen {
                        $color_cache.unseen.attrs
                    } else {
                        $color_cache.$field.attrs
                    },
                }
            }};
            ($color_cache:expr, $unseen:expr, $highlighted:expr, $selected:expr  $(,)*) => {{
                ThemeAttribute {
                    fg: if $highlighted {
                        $color_cache.highlighted.fg
                    } else if $selected {
                        $color_cache.selected.fg
                    } else if $unseen {
                        $color_cache.unseen.fg
                    } else {
                        $color_cache.theme_default.fg
                    },
                    bg: if $highlighted {
                        $color_cache.highlighted.bg
                    } else if $selected {
                        $color_cache.selected.bg
                    } else if $unseen {
                        $color_cache.unseen.bg
                    } else {
                        $color_cache.theme_default.bg
                    },
                    attrs: if $highlighted {
                        $color_cache.highlighted.attrs
                    } else if $selected {
                        $color_cache.selected.attrs
                    } else if $unseen {
                        $color_cache.unseen.attrs
                    } else {
                        $color_cache.theme_default.attrs
                    },
                }
            }};
        }

        pub(super) fn draw_entry(
            grid: &mut CellBuffer,
            strings: &EntryStrings,
            area: Area,
            color_cache: &ColorCache,
            unseen: bool,
            highlighted: bool,
            selected: bool,
        ) {
            let (upper_left, bottom_right) = area;
            let width = width!(area);
            let row_attr = row_attr!(color_cache, unseen, highlighted, selected);
            /* draw flags */
            let (x, _) = write_string_to_grid(
                &strings.flag,
                grid,
                row_attr.fg,
                row_attr.bg,
                row_attr.attrs,
                area,
                None,
            );
            for c in grid.row_iter(x..(x + 3), get_y(upper_left)) {
                grid[c].set_bg(row_attr.bg);
            }
            let subject_attr = row_attr!(subject, color_cache, unseen, highlighted, selected);
            /* draw subject */
            let (mut x, _) = write_string_to_grid(
                &strings.subject,
                grid,
                subject_attr.fg,
                subject_attr.bg,
                subject_attr.attrs,
                (set_x(upper_left, x), bottom_right),
                None,
            );
            x += 1;
            for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
                let color = color.unwrap_or(color_cache.tag_default.bg);
                let (_x, _) = write_string_to_grid(
                    t,
                    grid,
                    color_cache.tag_default.fg,
                    color,
                    color_cache.tag_default.attrs,
                    (set_x(upper_left, x + 1), bottom_right),
                    None,
                );
                grid[set_x(upper_left, x)].set_bg(color);
                if _x < width {
                    grid[set_x(upper_left, _x)].set_bg(color).set_keep_bg(true);
                }
                for c in grid.row_iter((x + 1).._x, get_y(upper_left)) {
                    grid[c].set_keep_fg(true).set_keep_bg(true);
                }
                grid[set_x(upper_left, x)].set_keep_bg(true);
                x = _x + 1;
            }
            for c in grid.row_iter(x..width, get_y(upper_left)) {
                grid[c].set_ch(' ').set_fg(row_attr.fg).set_bg(row_attr.bg);
            }
            let upper_left = pos_inc(upper_left, (0, 1));
            let date_attr = row_attr!(date, color_cache, unseen, highlighted, selected,);
            /* Next line, draw date */

            let (x, _) = write_string_to_grid(
                &strings.date,
                grid,
                date_attr.fg,
                date_attr.bg,
                date_attr.attrs,
                (upper_left, bottom_right),
                None,
            );
            for c in grid.row_iter(x..(x + 4), get_y(upper_left)) {
                grid[c].set_ch('▁').set_fg(row_attr.fg).set_bg(row_attr.bg);
            }
            let from_attr = row_attr!(from, color_cache, unseen, highlighted, selected,);
            /* draw from */
            let (x, _) = write_string_to_grid(
                &strings.from,
                grid,
                from_attr.fg,
                from_attr.bg,
                from_attr.attrs,
                (pos_inc(upper_left, (x + 4, 0)), bottom_right),
                None,
            );

            for c in grid.row_iter(x..width, get_y(upper_left)) {
                grid[c].set_ch('▁').set_fg(row_attr.fg).set_bg(row_attr.bg);
            }
        }

        impl MailboxList {
            pub(super) fn highlight_double_line(
                &mut self,
                grid: &mut CellBuffer,
                area: Area,
                idx: usize,
                context: &Context,
            ) {
                let (unseen, selection) = if let Some(ref entry) = self.entries.get(&idx) {
                    (
                        entry.unseen,
                        self.state
                            .selection
                            .get(&entry.thread_node_hash)
                            .cloned()
                            .unwrap_or(false),
                    )
                } else {
                    (false, false)
                };
                let row_attr = row_attr!(
                    self.color_cache,
                    unseen,
                    self.state.cursor_pos == idx,
                    selection
                );

                let padding_fg = if unseen {
                    self.color_cache.unseen_padding.fg
                } else {
                    self.color_cache.padding.fg
                };

                copy_area(
                    grid,
                    &self.data_columns.columns[0],
                    area,
                    (
                        (0, 2 * idx),
                        pos_dec(self.data_columns.columns[0].size(), (1, 1)),
                    ),
                );
                let (upper_left, bottom_right) = area;
                let (width, _) = self.data_columns.columns[0].size();
                let (x, y) = upper_left;
                if self.state.cursor_pos == idx || selection {
                    for x in x..=get_x(bottom_right) {
                        grid[(x, y)]
                            .set_fg(row_attr.fg)
                            .set_bg(row_attr.bg)
                            .set_attrs(row_attr.attrs);

                        grid[(x, y + 1)]
                            .set_fg(row_attr.fg)
                            .set_bg(row_attr.bg)
                            .set_attrs(row_attr.attrs);
                    }
                }
                if width < width!(area) {
                    /* fill any remaining columns, if our view is wider than self.content */
                    for x in (x + width)..=get_x(bottom_right) {
                        grid[(x, y)]
                            .set_fg(row_attr.fg)
                            .set_bg(row_attr.bg)
                            .set_attrs(row_attr.attrs);

                        grid[(x, y + 1)]
                            .set_fg(row_attr.fg)
                            .set_bg(row_attr.bg)
                            .set_attrs(row_attr.attrs);
                    }
                }
            }
        }
    }

    mod single_row {
        use super::*;
        macro_rules! row_attr {
            ($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr  $(,)*) => {{
                ThemeAttribute {
                    fg: if $highlighted {
                        if $even {
                            $color_cache.even_highlighted.fg
                        } else {
                            $color_cache.odd_highlighted.fg
                        }
                    } else if $selected {
                        if $even {
                            $color_cache.even_selected.fg
                        } else {
                            $color_cache.odd_selected.fg
                        }
                    } else if $unseen {
                        if $even {
                            $color_cache.even_unseen.fg
                        } else {
                            $color_cache.odd_unseen.fg
                        }
                    } else if $even {
                        $color_cache.even.fg
                    } else {
                        $color_cache.odd.fg
                    },
                    bg: if $highlighted {
                        if $even {
                            $color_cache.even_highlighted.bg
                        } else {
                            $color_cache.odd_highlighted.bg
                        }
                    } else if $selected {
                        if $even {
                            $color_cache.even_selected.bg
                        } else {
                            $color_cache.odd_selected.bg
                        }
                    } else if $unseen {
                        if $even {
                            $color_cache.even_unseen.bg
                        } else {
                            $color_cache.odd_unseen.bg
                        }
                    } else if $even {
                        $color_cache.even.bg
                    } else {
                        $color_cache.odd.bg
                    },
                    attrs: if $highlighted {
                        if $even {
                            $color_cache.even_highlighted.attrs
                        } else {
                            $color_cache.odd_highlighted.attrs
                        }
                    } else if $selected {
                        if $even {
                            $color_cache.even_selected.attrs
                        } else {
                            $color_cache.odd_selected.attrs
                        }
                    } else if $unseen {
                        if $even {
                            $color_cache.even_unseen.attrs
                        } else {
                            $color_cache.odd_unseen.attrs
                        }
                    } else if $even {
                        $color_cache.even.attrs
                    } else {
                        $color_cache.odd.attrs
                    },
                }
            }};
        }

        pub(super) fn draw_entry(
            columns: &mut [CellBuffer; 12],
            idx: usize,
            strings: &EntryStrings,
            snoozed: bool,
            has_attachments: bool,
            unseen: bool,
            highlighted: bool,
            selected: bool,
            color_cache: &ColorCache,
        ) {
            let row_attr = row_attr!(color_cache, idx % 2 == 0, unseen, highlighted, selected);
            let min_width = (
                columns[0].size().0,
                columns[1].size().0,
                columns[2].size().0,
                columns[3].size().0,
                columns[4].size().0,
            );
            let (x, _) = write_string_to_grid(
                &idx.to_string(),
                &mut columns[0],
                row_attr.fg,
                row_attr.bg,
                row_attr.attrs,
                ((0, idx), (min_width.0, idx)),
                None,
            );
            for c in columns[0].row_iter(x..min_width.0, idx) {
                columns[0][c].set_bg(row_attr.bg);
            }
            let (x, _) = write_string_to_grid(
                &strings.date,
                &mut columns[1],
                row_attr.fg,
                row_attr.bg,
                row_attr.attrs,
                ((0, idx), (min_width.1.saturating_sub(1), idx)),
                None,
            );
            for c in columns[1].row_iter(x..min_width.1, idx) {
                columns[1][c].set_bg(row_attr.bg);
            }
            let (x, _) = write_string_to_grid(
                &strings.from,
                &mut columns[2],
                row_attr.fg,
                row_attr.bg,
                row_attr.attrs,
                ((0, idx), (min_width.2, idx)),
                None,
            );
            for c in columns[2].row_iter(x..min_width.2, idx) {
                columns[2][c].set_bg(row_attr.bg);
            }
            let (x, _) = write_string_to_grid(
                &strings.flag,
                &mut columns[3],
                row_attr.fg,
                row_attr.bg,
                row_attr.attrs,
                ((0, idx), (min_width.3, idx)),
                None,
            );
            for c in columns[3].row_iter(x..min_width.3, idx) {
                columns[3][c].set_bg(row_attr.bg);
            }
            let (x, _) = write_string_to_grid(
                &strings.subject,
                &mut columns[4],
                row_attr.fg,
                row_attr.bg,
                row_attr.attrs,
                ((0, idx), (min_width.4, idx)),
                None,
            );
            let x = {
                let mut x = x + 1;
                for (t, &color) in strings.tags.split_whitespace().zip(strings.tags.1.iter()) {
                    let color = color.unwrap_or(color_cache.tag_default.bg);
                    let (_x, _) = write_string_to_grid(
                        t,
                        &mut columns[4],
                        color_cache.tag_default.fg,
                        color,
                        color_cache.tag_default.attrs,
                        ((x + 1, idx), (min_width.4, idx)),
                        None,
                    );
                    for c in columns[4].row_iter(x..(x + 1), idx) {
                        columns[4][c].set_bg(color);
                    }
                    for c in columns[4].row_iter(_x..(_x + 1), idx) {
                        columns[4][c].set_bg(color).set_keep_bg(true);
                    }
                    for c in columns[4].row_iter((x + 1)..(_x + 1), idx) {
                        columns[4][c]
                            .set_keep_fg(true)
                            .set_keep_bg(true)
                            .set_keep_attrs(true);
                    }
                    for c in columns[4].row_iter(x..(x + 1), idx) {
                        columns[4][c].set_keep_bg(true);
                    }
                    x = _x + 1;
                }
                x
            };
            for c in columns[4].row_iter(x..min_width.4, idx) {
                columns[4][c].set_ch(' ');
                columns[4][c].set_bg(row_attr.bg);
            }
            match (snoozed, has_attachments) {
                (true, true) => {
                    columns[3][(0, idx)].set_fg(color_cache.attachment_flag.fg);
                    columns[3][(2, idx)].set_fg(color_cache.thread_snooze_flag.fg);
                }
                (true, false) => {
                    columns[3][(0, idx)].set_fg(color_cache.thread_snooze_flag.fg);
                }
                (false, true) => {
                    columns[3][(0, idx)].set_fg(color_cache.attachment_flag.fg);
                }
                (false, false) => {}
            }
        }

        pub(super) fn draw_loading_entry(
            columns: &mut [CellBuffer; 12],
            idx: usize,
            highlighted: bool,
            color_cache: &ColorCache,
        ) {
            let row_attr = row_attr!(color_cache, idx % 2 == 0, false, highlighted, false);
            write_string_to_grid(
                LOADING_STR,
                &mut columns[4],
                row_attr.fg,
                row_attr.bg,
                row_attr.attrs,
                ((0, idx), (LOADING_STR.len() - 1, idx)),
                None,
            );
        }

        impl MailboxList {
            pub(super) fn highlight_single_line(
                &mut self,
                grid: &mut CellBuffer,
                area: Area,
                idx: usize,
                context: &Context,
            ) {
                let (unseen, selection) = if let Some(ref entry) = self.entries.get(&idx) {
                    (
                        entry.unseen,
                        self.state
                            .selection
                            .get(&entry.thread_node_hash)
                            .cloned()
                            .unwrap_or(false),
                    )
                } else {
                    (false, false)
                };

                let row_attr = row_attr!(
                    self.color_cache,
                    idx % 2 == 0,
                    unseen,
                    self.state.cursor_pos == idx,
                    selection,
                );
                let (upper_left, bottom_right) = area;

                for c in grid.row_iter(
                    get_x(upper_left)..(get_x(bottom_right) + 1),
                    get_y(upper_left),
                ) {
                    grid[c]
                        .set_fg(row_attr.fg)
                        .set_bg(row_attr.bg)
                        .set_attrs(row_attr.attrs);
                }
            }
        }
    }
}
