use super::{Prompt, Tree, TreeItem, TreeOp};
use crate::{
    compositor::{Component, Compositor, Context, EventResult},
    ctrl, key, shift, ui,
};
use anyhow::{bail, ensure, Result};
use helix_core::Position;
use helix_view::{
    editor::Action,
    graphics::{CursorKind, Modifier, Rect},
    input::{Event, KeyEvent},
    Editor,
};
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::{Path, PathBuf};
use std::{borrow::Cow, sync::mpsc::channel};
use std::{cmp::Ordering, sync::mpsc::Receiver};
use tui::{
    buffer::Buffer as Surface,
    text::{Span, Spans},
    widgets::{Block, Borders, Widget},
};

macro_rules! get_theme {
    ($theme: expr, $s1: expr, $s2: expr) => {
        $theme.try_get($s1).unwrap_or_else(|| $theme.get($s2))
    };
}

const ICONS: &'static [&'static str] =
    &["", "", "", "", "", "ﰟ", "", "", "", "ﯤ", "", "ﬥ"];

const ICONS_EXT: &'static [&'static str] = &[
    ".rs", ".md", ".js", ".c", ".png", ".svg", ".css", ".html", ".lua", ".ts", ".py", ".json",
];

const ICONS_COLORS: &'static [helix_view::theme::Color] = &[
    helix_view::theme::Color::Rgb(227, 134, 84),
    helix_view::theme::Color::LightCyan,
    helix_view::theme::Color::Yellow,
    helix_view::theme::Color::Blue,
    helix_view::theme::Color::Yellow,
    helix_view::theme::Color::Yellow,
    helix_view::theme::Color::Green,
    helix_view::theme::Color::Blue,
    helix_view::theme::Color::Red,
    helix_view::theme::Color::Blue,
    helix_view::theme::Color::Red,
];

#[derive(Debug, Clone, Copy, PartialEq)]
enum FileType {
    File,
    Dir,
    Exe,
    Placeholder,
    Parent,
    Root,
}

#[derive(Debug, Clone)]
struct FileInfo {
    file_type: FileType,
    expanded: bool,
    path: PathBuf,
}

impl FileInfo {
    fn new(path: PathBuf, file_type: FileType) -> Self {
        Self {
            path,
            file_type,
            expanded: false,
        }
    }

    fn root(path: PathBuf) -> Self {
        Self {
            file_type: FileType::Root,
            path,
            expanded: true,
        }
    }

    fn parent(path: &Path) -> Self {
        let p = path.parent().unwrap_or_else(|| Path::new(""));
        Self {
            file_type: FileType::Parent,
            path: p.to_path_buf(),
            expanded: false,
        }
    }

    fn get_text(&self) -> Cow<'static, str> {
        match self.file_type {
            FileType::Parent => "..".into(),
            FileType::Placeholder => "---".into(),
            FileType::Root => return format!("{}", self.path.display()).into(),
            FileType::File | FileType::Exe | FileType::Dir => self
                .path
                .file_name()
                .map_or("/".into(), |p| p.to_string_lossy().into_owned().into()),
        }
    }
}

impl TreeItem for FileInfo {
    type Params = State;
    fn text(&self, cx: &mut Context, selected: bool, state: &mut State) -> Spans {
        let text = self.get_text();
        let theme = &cx.editor.theme;

        let style = match self.file_type {
            FileType::Parent | FileType::Dir | FileType::Root => "ui.explorer.dir",
            FileType::File | FileType::Exe | FileType::Placeholder => "ui.explorer.file",
        };
        let mut style = theme.try_get(style).unwrap_or_else(|| theme.get("ui.text"));
        if selected {
            let patch = match state.focus {
                true => "ui.explorer.focus",
                false => "ui.explorer.unfocus",
            };
            if let Some(patch) = theme.try_get(patch) {
                style = style.patch(patch);
            } else {
                style = style.add_modifier(Modifier::REVERSED);
            }
        }
        Spans::from(Span::styled(text, style))
    }

    fn is_child(&self, other: &Self) -> bool {
        if let FileType::Parent = other.file_type {
            return false;
        }
        if let FileType::Placeholder = self.file_type {
            self.path == other.path
        } else {
            self.path.parent().map_or(false, |p| p == other.path)
        }
    }

    fn cmp(&self, other: &Self) -> Ordering {
        use FileType::*;
        match (self.file_type, other.file_type) {
            (Parent, _) => return Ordering::Less,
            (_, Parent) => return Ordering::Greater,
            (Root, _) => return Ordering::Less,
            (_, Root) => return Ordering::Greater,
            _ => {}
        };

        if self.path == other.path {
            match (self.file_type, other.file_type) {
                (_, Placeholder) => return Ordering::Less,
                (Placeholder, _) => return Ordering::Greater,
                _ => {}
            };
        }

        if let (Some(p1), Some(p2)) = (self.path.parent(), other.path.parent()) {
            if p1 == p2 {
                match (self.file_type, other.file_type) {
                    (Dir, File | Exe) => return Ordering::Less,
                    (File | Exe, Dir) => return Ordering::Greater,
                    _ => {}
                };
            }
        }
        self.path.cmp(&other.path)
    }

    fn get_childs(&self) -> Result<Vec<Self>> {
        match self.file_type {
            FileType::Root | FileType::Dir => {}
            _ => return Ok(vec![]),
        };
        let mut ret: Vec<_> = std::fs::read_dir(&self.path)?
            .filter_map(|entry| entry.ok())
            .filter_map(|entry| {
                entry.metadata().ok().map(|meta| {
                    let is_exe = false;
                    let file_type = match (meta.is_dir(), is_exe) {
                        (true, _) => FileType::Dir,
                        (_, false) => FileType::File,
                        (_, true) => FileType::Exe,
                    };
                    Self {
                        file_type,
                        path: self.path.join(entry.file_name()),
                        expanded: false,
                    }
                })
            })
            .collect();
        if ret.is_empty() {
            ret.push(Self {
                path: self.path.clone(),
                file_type: FileType::Placeholder,
                expanded: false,
            })
        }
        Ok(ret)
    }

    fn filter(&self, _cx: &mut Context, s: &str, _params: &mut Self::Params) -> bool {
        if s.is_empty() {
            false
        } else {
            self.get_text().contains(s)
        }
    }

    fn icon(&self) -> Option<(&'static str, &'static helix_view::theme::Color)> {
        return match self.file_type {
            FileType::Dir => {
                if self.expanded {
                    //Some(("", &helix_view::theme::Color::Yellow))
                    Some(("", &helix_view::theme::Color::Yellow))
                } else {
                    // Some(("", &helix_view::theme::Color::Yellow))
                    Some(("", &helix_view::theme::Color::Yellow))
                }
            }
            FileType::File => {
                for (i, ext) in ICONS_EXT.iter().enumerate() {
                    if self.get_text().ends_with(ext) {
                        let color = ICONS_COLORS
                            .iter()
                            .nth(i)
                            .unwrap_or(&helix_view::theme::Color::Blue);
                        return ICONS.iter().nth(i).map(|c| (*c, color));
                    }
                }
                return Some(("", &helix_view::theme::Color::LightBlue));
            }
            _ => None,
        };
    }
}

#[derive(Clone, Copy, Debug)]
enum PromptAction {
    Search(bool), // search next/search pre
    Mkdir,
    CreateFile,
    RemoveDir,
    RemoveFile,
    Filter,
}

#[derive(Clone, Debug)]
struct State {
    focus: bool,
    current_root: PathBuf,
}

impl State {
    fn new(focus: bool, current_root: PathBuf) -> Self {
        Self {
            focus,
            current_root,
        }
    }
}

pub struct Explorer {
    tree: Tree<FileInfo>,
    state: State,
    prompt: Option<(PromptAction, Prompt)>,
    #[allow(clippy::type_complexity)]
    on_next_key: Option<Box<dyn FnMut(&mut Context, &mut Self, &KeyEvent) -> EventResult>>,
    #[allow(clippy::type_complexity)]
    repeat_motion: Option<Box<dyn FnMut(&mut Self, PromptAction, &mut Context) + 'static>>,
    watcher: RecommendedWatcher,
    io_events: Receiver<Result<notify::Event, notify::Error>>,
}

impl Explorer {
    pub fn new(cx: &mut Context) -> Result<Self> {
        let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into());
        let items = Self::get_items(current_root.clone(), cx)?;
        let (mut watcher, receiver) = Self::create_watcher()?;
        watcher.watch(&current_root, RecursiveMode::Recursive)?;

        Ok(Self {
            tree: Tree::build_tree(items)
                .with_enter_fn(Self::toggle_current)
                .with_folded_fn(Self::fold_current),
            state: State::new(true, current_root),
            repeat_motion: None,
            prompt: None,
            on_next_key: None,
            watcher,
            io_events: receiver,
        })
    }

    pub fn set_selection(&mut self, path: &Path) {
        let info = if path.is_file() {
            FileInfo::new(path.into(), FileType::File)
        } else {
            FileInfo::new(path.into(), FileType::Dir)
        };
        self.tree.select(&info);
        self.tree.save_view();
    }

    pub fn new_explorer_recursion() -> Result<Self> {
        let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into());
        let parent = FileInfo::parent(&current_root);
        let root = FileInfo::root(current_root.clone());
        let mut tree = Tree::build_from_root(root, usize::MAX / 2)?
            .with_enter_fn(Self::toggle_current)
            .with_folded_fn(Self::fold_current);
        tree.insert_current_level(parent);
        let (mut watcher, receiver) = Self::create_watcher()?;
        watcher.watch(&current_root, RecursiveMode::Recursive)?;

        Ok(Self {
            tree,
            state: State::new(true, current_root),
            repeat_motion: None,
            prompt: None,
            on_next_key: None,
            watcher,
            io_events: receiver,
        })
    }

    pub fn focus(&mut self) {
        self.state.focus = true
    }

    pub fn unfocus(&mut self) {
        self.state.focus = false;
    }

    pub fn is_focus(&self) -> bool {
        self.state.focus
    }

    fn get_items(p: PathBuf, cx: &mut Context) -> Result<Vec<FileInfo>> {
        let mut items = vec![FileInfo::parent(p.as_path())];
        let root = FileInfo::root(p);
        let childs = root.get_childs()?;
        if cx.editor.config().explorer.is_tree() {
            items.push(root)
        }
        items.extend(childs);
        Ok(items)
    }

    pub fn handle_changes(&mut self, cx: &mut Context) -> Result<()> {
        if let Ok(o) = self.io_events.try_recv() {
            match o {
                Ok(_) => self.refresh(cx),
                Err(e) => Err(e.into()),
            }
        } else {
            Ok(())
        }
    }

    fn refresh(&mut self, cx: &mut Context) -> Result<()> {
        let items = Self::get_items(self.state.current_root.clone(), cx)?;
        self.tree.replace_with_new_items(items);

        Ok(())
    }

    fn render_preview(&mut self, area: Rect, surface: &mut Surface, editor: &Editor) {
        if area.height <= 2 || area.width < 60 {
            return;
        }
        let item = self.tree.current().item();
        if item.file_type == FileType::Placeholder {
            return;
        }
        let head_area = render_block(
            area.clip_bottom(area.height - 2),
            surface,
            Borders::BOTTOM,
            None,
        );
        let path_str = format!("{}", item.path.display());
        surface.set_stringn(
            head_area.x,
            head_area.y,
            path_str,
            head_area.width as usize,
            get_theme!(editor.theme, "ui.explorer.dir", "ui.text"),
        );

        let body_area = area.clip_top(2);
        let style = editor.theme.get("ui.text");
        if let Ok(preview_content) = get_preview(&item.path, body_area.height as usize) {
            preview_content
                .into_iter()
                .enumerate()
                .for_each(|(row, line)| {
                    surface.set_stringn(
                        body_area.x,
                        body_area.y + row as u16,
                        line,
                        body_area.width as usize,
                        style,
                    );
                })
        }
    }

    fn watch(&mut self, path: &Path) -> Result<()> {
        self.watcher.watch(path, RecursiveMode::Recursive)?;

        Ok(())
    }

    fn unwatch(&mut self, path: &Path) -> Result<()> {
        self.watcher.unwatch(path)?;

        Ok(())
    }

    fn create_watcher() -> Result<(
        RecommendedWatcher,
        Receiver<Result<notify::Event, notify::Error>>,
    )> {
        let (tx, rx) = channel();
        let watcher = RecommendedWatcher::new(tx, Config::default())?;

        Ok((watcher, rx))
    }

    fn new_search_prompt(&mut self, search_next: bool) {
        self.tree.save_view();
        self.prompt = Some((
            PromptAction::Search(search_next),
            Prompt::new("search: ".into(), None, ui::completers::none, |_, _, _| {}),
        ))
    }

    fn new_filter_prompt(&mut self) {
        self.tree.save_view();
        self.prompt = Some((
            PromptAction::Filter,
            Prompt::new("filter: ".into(), None, ui::completers::none, |_, _, _| {}),
        ))
    }

    fn new_mkdir_prompt(&mut self) {
        self.prompt = Some((
            PromptAction::Mkdir,
            Prompt::new("mkdir: ".into(), None, ui::completers::none, |_, _, _| {}),
        ));
    }

    fn new_create_file_prompt(&mut self) {
        self.prompt = Some((
            PromptAction::CreateFile,
            Prompt::new(
                "create file: ".into(),
                None,
                ui::completers::none,
                |_, _, _| {},
            ),
        ));
    }

    fn new_remove_file_prompt(&mut self, cx: &mut Context) {
        let item = self.tree.current_item();
        let check = || {
            ensure!(item.file_type != FileType::Placeholder, "The path is empty");
            ensure!(
                item.file_type != FileType::Parent,
                "can not remove parent dir"
            );
            ensure!(item.path.is_file(), "The path is not a file");
            let doc = cx.editor.document_by_path(&item.path);
            ensure!(doc.is_none(), "The file is opened");
            Ok(())
        };
        if let Err(e) = check() {
            cx.editor.set_error(format!("{e}"));
            return;
        }
        let p = format!("remove file: {}, YES? ", item.path.display());
        self.prompt = Some((
            PromptAction::RemoveFile,
            Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}),
        ));
    }

    fn new_remove_dir_prompt(&mut self, cx: &mut Context) {
        let item = self.tree.current_item();
        let check = || {
            ensure!(item.file_type != FileType::Placeholder, "The path is empty");
            ensure!(
                item.file_type != FileType::Parent,
                "can not remove parent dir"
            );
            ensure!(item.path.is_dir(), "The path is not a dir");
            let doc = cx.editor.documents().find(|doc| {
                doc.path()
                    .map(|p| p.starts_with(&item.path))
                    .unwrap_or(false)
            });
            ensure!(doc.is_none(), "There are files opened under the dir");
            Ok(())
        };
        if let Err(e) = check() {
            cx.editor.set_error(format!("{e}"));
            return;
        }
        let p = format!("remove dir: {}, YES? ", item.path.display());
        self.prompt = Some((
            PromptAction::RemoveDir,
            Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}),
        ));
    }

    fn fold_current(item: &mut FileInfo, _cx: &mut Context, _state: &mut State) {
        if item.path.is_dir() {
            item.expanded = false;
        }
    }

    fn toggle_current(
        item: &mut FileInfo,
        cx: &mut Context,
        state: &mut State,
    ) -> TreeOp<FileInfo> {
        if item.file_type == FileType::Placeholder {
            return TreeOp::Noop;
        }
        if item.path == Path::new("") {
            return TreeOp::Noop;
        }
        let meta = match std::fs::metadata(&item.path) {
            Ok(meta) => meta,
            Err(e) => {
                cx.editor.set_error(format!("{e}"));
                return TreeOp::Noop;
            }
        };
        if meta.is_file() {
            if let Err(e) = cx.editor.open(&item.path.clone(), Action::Replace) {
                cx.editor.set_error(format!("{e}"));
            }
            state.focus = false;
            return TreeOp::Noop;
        }

        if item.path.is_dir() {
            item.expanded = true;
            if cx.editor.config().explorer.is_list() || item.file_type == FileType::Parent {
                match Self::get_items(item.path.clone(), cx) {
                    Ok(items) => {
                        state.current_root = item.path.clone();
                        return TreeOp::ReplaceTree(items);
                    }
                    Err(e) => cx.editor.set_error(format!("{e}")),
                }
            } else {
                return TreeOp::GetChildsAndInsert;
            }
        }
        cx.editor.set_error("unkonw file type");
        TreeOp::Noop
    }

    fn render_float(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
        let background = cx.editor.theme.get("ui.background");
        let column_width = cx.editor.config().explorer.column_width as u16;
        surface.clear_with(area, background);
        let area = render_block(area, surface, Borders::ALL, None);

        let mut preview_area = area.clip_left(column_width + 1);
        if let Some((_, prompt)) = self.prompt.as_mut() {
            let area = preview_area.clip_bottom(2);
            let promp_area = render_block(
                preview_area.clip_top(area.height),
                surface,
                Borders::TOP,
                None,
            );
            prompt.render(promp_area, surface, cx);
            preview_area = area;
        }
        self.render_preview(preview_area, surface, cx.editor);

        let list_area = render_block(
            area.clip_right(preview_area.width),
            surface,
            Borders::RIGHT,
            None,
        );
        self.tree.render(list_area, surface, cx, &mut self.state);
    }

    fn render_embed(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
        let config = &cx.editor.config().explorer;
        let side_area = area
            .with_width(area.width.min(config.column_width as u16 + 2))
            .clip_bottom(1);

        let background = cx.editor.theme.get("ui.statusline");
        surface.clear_with(side_area, background);

        let preview_area = area.clip_left(side_area.width).clip_bottom(2);
        let prompt_area = area.clip_top(side_area.height);

        let border_style = cx.editor.theme.get("ui.explorer.border");
        let list_area = render_block(
            side_area.clip_left(1),
            surface,
            Borders::RIGHT,
            Some(border_style),
        )
        .clip_bottom(1);
        self.tree.render(list_area, surface, cx, &mut self.state);

        {
            let statusline = if self.is_focus() {
                cx.editor.theme.get("ui.statusline")
            } else {
                cx.editor.theme.get("ui.statusline.inactive")
            };
            let area = side_area.clip_top(list_area.height).clip_right(1);
            surface.clear_with(area, statusline);
            // surface.set_string_truncated(
            //     area.x,
            //     area.y,
            //     &self.path_state.root.to_string_lossy(),
            //     area.width as usize,
            //     |_| statusline,
            //     true,
            //     true,
            // );
        }

        if self.is_focus() {
            if preview_area.width < 30 || preview_area.height < 3 {
                return;
            }
            let width = preview_area.width.min(90);
            let mut y = self.tree.row().saturating_sub(1) as u16;
            let height = (preview_area.height).min(25);
            if (height + y) > preview_area.height {
                y = preview_area.height - height;
            }
            let area = Rect::new(preview_area.x, y, width, height);
            surface.clear_with(area, background);
            let area = render_block(area, surface, Borders::all(), None);
            self.render_preview(area, surface, cx.editor);
        }

        if let Some((_, prompt)) = self.prompt.as_mut() {
            prompt.render_prompt(prompt_area, surface, cx)
        }
    }

    fn handle_filter_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
        let (action, mut prompt) = self.prompt.take().unwrap();
        match event {
            key!(Tab) | key!(Down) | ctrl!('j') => {
                self.tree.clean_recycle();
                return self
                    .tree
                    .handle_event(Event::Key(event.clone()), cx, &mut self.state);
            }
            key!(Enter) => {
                self.tree.clean_recycle();
                return self
                    .tree
                    .handle_event(Event::Key(event.clone()), cx, &mut self.state);
            }
            key!(Esc) | ctrl!('c') => self.tree.restore_recycle(),
            _ => {
                if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) {
                    self.tree.filter(prompt.line(), cx, &mut self.state);
                }
                self.prompt = Some((action, prompt));
            }
        };
        EventResult::Consumed(None)
    }

    fn handle_search_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
        let (action, mut prompt) = self.prompt.take().unwrap();
        let search_next = match action {
            PromptAction::Search(search_next) => search_next,
            _ => return EventResult::Ignored(None),
        };
        match event {
            key!(Tab) | key!(Down) | ctrl!('j') => {
                return self
                    .tree
                    .handle_event(Event::Key(event.clone()), cx, &mut self.state)
            }
            key!(Enter) => {
                let search_str = prompt.line().clone();
                if !search_str.is_empty() {
                    self.repeat_motion = Some(Box::new(move |explorer, action, cx| {
                        if let PromptAction::Search(is_next) = action {
                            explorer.tree.save_view();
                            if is_next == search_next {
                                explorer
                                    .tree
                                    .search_next(cx, &search_str, &mut explorer.state);
                            } else {
                                explorer
                                    .tree
                                    .search_pre(cx, &search_str, &mut explorer.state);
                            }
                        }
                    }))
                } else {
                    self.repeat_motion = None;
                }
                return self
                    .tree
                    .handle_event(Event::Key(event.clone()), cx, &mut self.state);
            }
            key!(Esc) | ctrl!('c') => self.tree.restore_view(),
            _ => {
                if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) {
                    if search_next {
                        self.tree.search_next(cx, prompt.line(), &mut self.state);
                    } else {
                        self.tree.search_pre(cx, prompt.line(), &mut self.state);
                    }
                }
                self.prompt = Some((action, prompt));
            }
        };
        EventResult::Consumed(None)
    }

    fn handle_prompt_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
        match &self.prompt {
            Some((PromptAction::Search(_), _)) => return self.handle_search_event(event, cx),
            Some((PromptAction::Filter, _)) => return self.handle_filter_event(event, cx),
            _ => {}
        };
        let (action, mut prompt) = match self.prompt.take() {
            Some((action, p)) => (action, p),
            _ => return EventResult::Ignored(None),
        };
        let line = prompt.line();
        match (action, event) {
            (PromptAction::Mkdir, key!(Enter)) => {
                if let Err(e) = self.new_path(line, true) {
                    cx.editor.set_error(format!("{e}"))
                }
            }
            (PromptAction::CreateFile, key!(Enter)) => {
                if let Err(e) = self.new_path(line, false) {
                    cx.editor.set_error(format!("{e}"))
                }
            }
            (PromptAction::RemoveDir, key!(Enter)) => {
                let item = self.tree.current_item();
                if let Err(e) = std::fs::remove_dir_all(&item.path) {
                    cx.editor.set_error(format!("{e}"));
                } else {
                    self.tree.fold_current_child();
                    self.tree.remove_current();
                }
            }
            (PromptAction::RemoveFile, key!(Enter)) => {
                if line == "YES" {
                    let item = self.tree.current_item();
                    if let Err(e) = std::fs::remove_file(&item.path) {
                        cx.editor.set_error(format!("{e}"));
                    } else {
                        self.tree.remove_current();
                    }
                }
            }
            (_, key!(Esc) | ctrl!('c')) => {}
            _ => {
                prompt.handle_event(&Event::Key(*event), cx);
                self.prompt = Some((action, prompt));
            }
        }
        EventResult::Consumed(None)
    }

    fn new_path(&mut self, file_name: &str, is_dir: bool) -> Result<()> {
        let current = self.tree.current_item();
        let current_parent = if current.file_type == FileType::Placeholder {
            &current.path
        } else {
            current
                .path
                .parent()
                .ok_or_else(|| anyhow::anyhow!("can not get parent dir"))?
        };
        let p = helix_core::path::get_normalized_path(&current_parent.join(file_name));
        match p.parent() {
            Some(p) if p == current_parent => {}
            _ => bail!("The file name is not illegal"),
        };

        let f = if is_dir {
            std::fs::create_dir(&p)?;
            FileInfo::new(p, FileType::Dir)
        } else {
            let mut fd = std::fs::OpenOptions::new();
            fd.create_new(true).write(true).open(&p)?;
            FileInfo::new(p, FileType::File)
        };
        if current.file_type == FileType::Placeholder {
            self.tree.replace_current(f);
        } else {
            self.tree.insert_current_level(f);
        }
        Ok(())
    }
}

impl Component for Explorer {
    /// Process input events, return true if handled.
    fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
        let key_event = match event {
            Event::Key(event) => *event,
            Event::Resize(..) => return EventResult::Consumed(None),
            _ => return EventResult::Ignored(None),
        };
        if !self.is_focus() {
            return EventResult::Ignored(None);
        }
        if let Some(mut on_next_key) = self.on_next_key.take() {
            return on_next_key(cx, self, &key_event);
        }

        if let EventResult::Consumed(c) = self.handle_prompt_event(&key_event, cx) {
            return EventResult::Consumed(c);
        }

        let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
            if let Some(editor) = compositor.find::<ui::EditorView>() {
                editor.explorer = None;
            }
        })));

        match key_event {
            key!(Esc) => self.unfocus(),
            ctrl!('c') => return close_fn,
            key!('n') => {
                if let Some(mut repeat_motion) = self.repeat_motion.take() {
                    repeat_motion(self, PromptAction::Search(true), cx);
                    self.repeat_motion = Some(repeat_motion);
                }
            }
            shift!('N') => {
                if let Some(mut repeat_motion) = self.repeat_motion.take() {
                    repeat_motion(self, PromptAction::Search(false), cx);
                    self.repeat_motion = Some(repeat_motion);
                }
            }
            key!('b') => {
                if let Some(p) = self.state.current_root.parent() {
                    match Self::get_items(p.to_path_buf(), cx) {
                        Ok(items) => {
                            self.state.current_root = p.to_path_buf();
                            self.tree = Tree::build_tree(items)
                                .with_enter_fn(Self::toggle_current)
                                .with_folded_fn(Self::fold_current);
                        }
                        Err(e) => cx.editor.set_error(format!("{e}")),
                    }
                }
            }
            key!('f') => self.new_filter_prompt(),
            key!('/') => self.new_search_prompt(true),
            key!('?') => self.new_search_prompt(false),
            key!('m') => {
                self.on_next_key = Some(Box::new(|_, explorer, event| {
                    match event {
                        key!('d') => explorer.new_mkdir_prompt(),
                        key!('f') => explorer.new_create_file_prompt(),
                        _ => return EventResult::Ignored(None),
                    };
                    EventResult::Consumed(None)
                }));
            }
            key!('r') => {
                self.on_next_key = Some(Box::new(|cx, explorer, event| {
                    match event {
                        key!('d') => explorer.new_remove_dir_prompt(cx),
                        key!('f') => explorer.new_remove_file_prompt(cx),
                        _ => return EventResult::Ignored(None),
                    };
                    EventResult::Consumed(None)
                }));
            }
            _ => {
                self.tree
                    .handle_event(Event::Key(key_event.clone()), cx, &mut self.state);
            }
        }

        EventResult::Consumed(None)
    }

    fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
        if area.width < 10 || area.height < 5 {
            cx.editor.set_error("explorer render area is too small");
            return;
        }
        let config = &cx.editor.config().explorer;
        if config.is_embed() {
            self.render_embed(area, surface, cx);
        } else {
            self.render_float(area, surface, cx);
        }
    }

    fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
        let prompt = match self.prompt.as_ref() {
            Some((_, prompt)) => prompt,
            None => return (None, CursorKind::Hidden),
        };
        let config = &editor.config().explorer;
        let (x, y) = if config.is_overlay() {
            let colw = config.column_width as u16;
            if area.width > colw {
                (area.x + colw + 2, area.y + area.height - 2)
            } else {
                return (None, CursorKind::Hidden);
            }
        } else {
            (area.x, area.y + area.height - 1)
        };
        prompt.cursor(Rect::new(x, y, area.width, 1), editor)
    }
}

fn get_preview(p: impl AsRef<Path>, max_line: usize) -> Result<Vec<String>> {
    let p = p.as_ref();
    if p.is_dir() {
        return Ok(p
            .read_dir()?
            .filter_map(|entry| entry.ok())
            .take(max_line)
            .map(|entry| {
                if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
                    format!("{}/", entry.file_name().to_string_lossy())
                } else {
                    format!("{}", entry.file_name().to_string_lossy())
                }
            })
            .collect());
    }

    ensure!(p.is_file(), "path: {} is not file or dir", p.display());
    use std::fs::OpenOptions;
    use std::io::BufRead;
    let mut fd = OpenOptions::new();
    fd.read(true);
    let fd = fd.open(p)?;
    Ok(std::io::BufReader::new(fd)
        .lines()
        .take(max_line)
        .filter_map(|line| line.ok())
        .map(|line| line.replace('\t', "    "))
        .collect())
}

fn render_block(
    area: Rect,
    surface: &mut Surface,
    borders: Borders,
    border_style: Option<helix_view::theme::Style>,
) -> Rect {
    let mut block = Block::default().borders(borders);
    if let Some(style) = border_style {
        block = block.border_style(style);
    }
    //let block = Block::default();
    let inner = block.inner(area);
    block.render(area, surface);
    inner
}
