use std::cmp::Ordering;
use std::iter::Peekable;

use anyhow::Result;

use crate::{
    compositor::{Context, EventResult},
    ctrl, key, shift,
};
use helix_core::unicode::width::UnicodeWidthStr;
use helix_view::{
    graphics::Rect,
    input::{Event, KeyEvent},
};
use tui::{buffer::Buffer as Surface, text::Spans};

pub trait TreeItem: Sized {
    type Params;

    fn text(&self, cx: &mut Context, selected: bool, params: &mut Self::Params) -> Spans;
    fn is_child(&self, other: &Self) -> bool;
    fn cmp(&self, other: &Self) -> Ordering;
    fn icon(&self) -> Option<(&'static str, &'static helix_view::theme::Color)>;

    fn filter(&self, cx: &mut Context, s: &str, params: &mut Self::Params) -> bool {
        self.text(cx, false, params)
            .0
            .into_iter()
            .map(|s| s.content)
            .collect::<Vec<_>>()
            .concat()
            .contains(s)
    }

    fn get_childs(&self) -> Result<Vec<Self>> {
        Ok(vec![])
    }
}

fn tree_item_cmp<T: TreeItem>(item1: &T, item2: &T) -> Ordering {
    if item1.is_child(item2) {
        return Ordering::Greater;
    }
    if item2.is_child(item1) {
        return Ordering::Less;
    }

    T::cmp(item1, item2)
}

fn vec_to_tree<T: TreeItem>(mut items: Vec<T>, level: usize) -> Vec<Elem<T>> {
    fn get_childs<T, Iter>(iter: &mut Peekable<Iter>, elem: &mut Elem<T>)
    where
        T: TreeItem,
        Iter: Iterator<Item = T>,
    {
        let level = elem.level + 1;
        loop {
            if !iter.peek().map_or(false, |next| next.is_child(&elem.item)) {
                break;
            }
            let mut child = Elem::new(iter.next().unwrap(), level);
            if iter.peek().map_or(false, |nc| nc.is_child(&child.item)) {
                get_childs(iter, &mut child);
            }
            elem.folded.push(child);
        }
    }

    items.sort_by(tree_item_cmp);
    let mut elems = Vec::with_capacity(items.len());
    let mut iter = items.into_iter().peekable();
    while let Some(item) = iter.next() {
        let mut elem = Elem::new(item, level);
        if iter.peek().map_or(false, |next| next.is_child(&elem.item)) {
            get_childs(&mut iter, &mut elem);
        }
        expand_elems(&mut elems, elem);
    }
    elems
}

// return total elems's count contain self
fn get_elems_recursion<T: TreeItem>(t: &mut Elem<T>, depth: usize) -> Result<usize> {
    let mut childs = t.item.get_childs()?;
    childs.sort_by(tree_item_cmp);
    let mut elems = Vec::with_capacity(childs.len());
    let level = t.level + 1;
    let mut total = 1;
    for child in childs {
        let mut elem = Elem::new(child, level);
        let count = if depth > 0 {
            get_elems_recursion(&mut elem, depth - 1)?
        } else {
            1
        };
        elems.push(elem);
        total += count;
    }
    t.folded = elems;
    Ok(total)
}

fn expand_elems<T: TreeItem>(dist: &mut Vec<Elem<T>>, mut t: Elem<T>) {
    let childs = std::mem::take(&mut t.folded);
    dist.push(t);
    for child in childs {
        expand_elems(dist, child)
    }
}

pub enum TreeOp<T> {
    Noop,
    Restore,
    InsertChild(Vec<T>),
    GetChildsAndInsert,
    ReplaceTree(Vec<T>),
}

pub struct Elem<T> {
    item: T,
    level: usize,
    folded: Vec<Self>,
}

impl<T: Clone> Clone for Elem<T> {
    fn clone(&self) -> Self {
        Self {
            item: self.item.clone(),
            level: self.level,
            folded: self.folded.clone(),
        }
    }
}

impl<T> Elem<T> {
    pub fn new(item: T, level: usize) -> Self {
        Self {
            item,
            level,
            folded: vec![],
        }
    }

    pub fn item(&self) -> &T {
        &self.item
    }
}

pub struct Tree<T: TreeItem> {
    items: Vec<Elem<T>>,
    recycle: Option<(String, Vec<Elem<T>>)>,
    selected: usize,
    save_view: (usize, usize), // (selected, row)
    row: usize,
    col: usize,
    max_len: usize,
    count: usize,
    tree_symbol_style: String,
    #[allow(clippy::type_complexity)]
    pre_render: Option<Box<dyn Fn(&mut Self, Rect) + 'static>>,
    #[allow(clippy::type_complexity)]
    on_opened_fn:
        Option<Box<dyn FnMut(&mut T, &mut Context, &mut T::Params) -> TreeOp<T> + 'static>>,
    #[allow(clippy::type_complexity)]
    on_folded_fn: Option<Box<dyn FnMut(&mut T, &mut Context, &mut T::Params) + 'static>>,
    #[allow(clippy::type_complexity)]
    on_next_key: Option<Box<dyn FnMut(&mut Context, &mut Self, KeyEvent)>>,
}

impl<T: TreeItem> Tree<T> {
    pub fn new(items: Vec<Elem<T>>) -> Self {
        Self {
            items,
            recycle: None,
            selected: 0,
            save_view: (0, 0),
            row: 0,
            col: 0,
            max_len: 0,
            count: 0,
            tree_symbol_style: "ui.explorer.guide".into(),
            pre_render: None,
            on_opened_fn: None,
            on_folded_fn: None,
            on_next_key: None,
        }
    }

    pub fn replace_with_new_items(&mut self, items: Vec<T>) {
        let old = std::mem::replace(self, Self::new(vec_to_tree(items, 0)));
        self.on_opened_fn = old.on_opened_fn;
        self.on_folded_fn = old.on_folded_fn;
        self.tree_symbol_style = old.tree_symbol_style;
    }

    pub fn build_tree(items: Vec<T>) -> Self {
        Self::new(vec_to_tree(items, 0))
    }

    pub fn build_from_root(t: T, depth: usize) -> Result<Self> {
        let mut elem = Elem::new(t, 0);
        let count = get_elems_recursion(&mut elem, depth)?;
        let mut elems = Vec::with_capacity(count);
        expand_elems(&mut elems, elem);
        Ok(Self::new(elems))
    }

    pub fn with_enter_fn<F>(mut self, f: F) -> Self
    where
        F: FnMut(&mut T, &mut Context, &mut T::Params) -> TreeOp<T> + 'static,
    {
        self.on_opened_fn = Some(Box::new(f));
        self
    }

    pub fn with_folded_fn<F>(mut self, f: F) -> Self
    where
        F: FnMut(&mut T, &mut Context, &mut T::Params) + 'static,
    {
        self.on_folded_fn = Some(Box::new(f));
        self
    }

    pub fn tree_symbol_style(mut self, style: String) -> Self {
        self.tree_symbol_style = style;
        self
    }

    fn next_item(&self) -> Option<&Elem<T>> {
        self.items.get(self.selected + 1)
    }

    fn next_not_descendant_pos(&self, index: usize) -> usize {
        let item = &self.items[index];
        self.find(index + 1, false, |n| n.level <= item.level)
            .unwrap_or(self.items.len())
    }

    fn find_parent(&self, index: usize) -> Option<usize> {
        let item = &self.items[index];
        self.find(index, true, |p| p.level < item.level)
    }

    // rev start: start - 1
    fn find<F>(&self, start: usize, rev: bool, f: F) -> Option<usize>
    where
        F: FnMut(&Elem<T>) -> bool,
    {
        let iter = self.items.iter();
        if rev {
            iter.take(start).rposition(f)
        } else {
            iter.skip(start).position(f).map(|p| p + start)
        }
    }
}

impl<T: TreeItem> Tree<T> {
    pub fn on_enter(&mut self, cx: &mut Context, params: &mut T::Params) {
        if self.items.is_empty() {
            return;
        }
        if let Some(next_level) = self.next_item().map(|elem| elem.level) {
            let current = &mut self.items[self.selected];
            let current_level = current.level;
            if next_level > current_level {
                if let Some(mut on_folded_fn) = self.on_folded_fn.take() {
                    on_folded_fn(&mut current.item, cx, params);
                    self.on_folded_fn = Some(on_folded_fn);
                }
                self.fold_current_child();
                return;
            }
        }

        if let Some(mut on_open_fn) = self.on_opened_fn.take() {
            let mut f = || {
                let current = &mut self.items[self.selected];
                let items = match on_open_fn(&mut current.item, cx, params) {
                    TreeOp::Restore => {
                        let inserts = std::mem::take(&mut current.folded);
                        let _: Vec<_> = self
                            .items
                            .splice(self.selected + 1..self.selected + 1, inserts)
                            .collect();
                        return;
                    }
                    TreeOp::InsertChild(items) => items,
                    TreeOp::GetChildsAndInsert => match current.item.get_childs() {
                        Ok(items) => items,
                        Err(e) => return cx.editor.set_error(format!("{e}")),
                    },
                    TreeOp::ReplaceTree(items) => return self.replace_with_new_items(items),
                    TreeOp::Noop => return,
                };
                current.folded = vec![];
                let inserts = vec_to_tree(items, current.level + 1);
                let _: Vec<_> = self
                    .items
                    .splice(self.selected + 1..self.selected + 1, inserts)
                    .collect();
            };
            f();
            self.on_opened_fn = Some(on_open_fn)
        } else {
            let current = &mut self.items[self.selected];
            let inserts = std::mem::take(&mut current.folded);
            let _: Vec<_> = self
                .items
                .splice(self.selected + 1..self.selected + 1, inserts)
                .collect();
        }
    }

    pub fn fold_current_level(&mut self) {
        let start = match self.find_parent(self.selected) {
            Some(start) => start,
            None => return,
        };
        self.selected = start;
        self.fold_current_child();
    }

    pub fn fold_current_child(&mut self) {
        if self.selected + 1 >= self.items.len() {
            return;
        }
        let pos = self.next_not_descendant_pos(self.selected);
        if self.selected < pos {
            self.items[self.selected].folded = self.items.drain(self.selected + 1..pos).collect();
        }
    }

    pub fn search_next(&mut self, cx: &mut Context, s: &str, params: &mut T::Params) {
        let skip = self.save_view.0 + 1;
        self.selected = self
            .find(skip, false, |e| e.item.filter(cx, s, params))
            .unwrap_or(self.save_view.0);

        self.row = (self.save_view.1 + self.selected).saturating_sub(self.save_view.0);
    }

    pub fn search_pre(&mut self, cx: &mut Context, s: &str, params: &mut T::Params) {
        let take = self.save_view.0;
        self.selected = self
            .find(take, true, |e| e.item.filter(cx, s, params))
            .unwrap_or(self.save_view.0);

        self.row = (self.save_view.1 + self.selected).saturating_sub(self.save_view.0);
    }

    pub fn move_down(&mut self, rows: usize) {
        let len = self.items.len();
        if len > 0 {
            self.selected = std::cmp::min(self.selected + rows, len.saturating_sub(1));
            self.row = std::cmp::min(self.selected, self.row + rows);
        }
    }

    pub fn move_up(&mut self, rows: usize) {
        let len = self.items.len();
        if len > 0 {
            self.selected = self.selected.saturating_sub(rows);
            self.row = std::cmp::min(self.selected, self.row.saturating_sub(rows));
        }
    }

    pub fn move_left(&mut self, cols: usize) {
        self.col = self.col.saturating_sub(cols);
    }

    pub fn move_right(&mut self, cols: usize) {
        self.pre_render = Some(Box::new(move |tree: &mut Self, area: Rect| {
            let max_scroll = tree.max_len.saturating_sub(area.width as usize);
            tree.col = max_scroll.min(tree.col + cols);
        }));
    }

    pub fn move_down_half_page(&mut self) {
        self.pre_render = Some(Box::new(|tree: &mut Self, area: Rect| {
            tree.move_down((area.height / 2) as usize);
        }));
    }

    pub fn move_up_half_page(&mut self) {
        self.pre_render = Some(Box::new(|tree: &mut Self, area: Rect| {
            tree.move_up((area.height / 2) as usize);
        }));
    }

    pub fn move_down_page(&mut self) {
        self.pre_render = Some(Box::new(|tree: &mut Self, area: Rect| {
            tree.move_down((area.height) as usize);
        }));
    }

    pub fn move_up_page(&mut self) {
        self.pre_render = Some(Box::new(|tree: &mut Self, area: Rect| {
            tree.move_up((area.height) as usize);
        }));
    }

    pub fn save_view(&mut self) {
        self.save_view = (self.selected, self.row);
    }

    pub fn restore_view(&mut self) {
        (self.selected, self.row) = self.save_view;
    }

    pub fn current(&self) -> &Elem<T> {
        &self.items[self.selected]
    }

    pub fn current_item(&self) -> &T {
        &self.items[self.selected].item
    }

    pub fn row(&self) -> usize {
        self.row
    }

    pub fn remove_current(&mut self) -> T {
        let elem = self.items.remove(self.selected);
        self.selected = self.selected.saturating_sub(1);
        elem.item
    }

    pub fn replace_current(&mut self, item: T) {
        self.items[self.selected].item = item;
    }

    pub fn select(&mut self, select_item: &T) {
        let selected = self
            .items
            .iter()
            .enumerate()
            .filter(|(_, i)| i.item.cmp(select_item) == Ordering::Equal)
            .next();
        if let Some((idx, _)) = selected {
            self.selected = idx;
            self.row = idx;
        }
    }

    pub fn insert_current_level(&mut self, item: T) {
        let current = self.current();
        let level = current.level;
        let pos = match current.item.cmp(&item) {
            Ordering::Less => self
                .find(self.selected + 1, false, |e| {
                    e.level < level || (e.level == level && e.item.cmp(&item) != Ordering::Less)
                })
                .unwrap_or(self.items.len()),

            Ordering::Greater => {
                match self.find(self.selected, true, |elem| {
                    elem.level < level
                        || (elem.level == level && elem.item.cmp(&item) != Ordering::Greater)
                }) {
                    Some(p) if self.items[p].level == level => self.next_not_descendant_pos(p),
                    Some(p) => p + 1,
                    None => 0,
                }
            }
            Ordering::Equal => self.selected + 1,
        };
        self.items.insert(pos, Elem::new(item, level));
    }
}

impl<T: TreeItem> Tree<T> {
    pub fn render(
        &mut self,
        area: Rect,
        surface: &mut Surface,
        cx: &mut Context,
        params: &mut T::Params,
    ) {
        if let Some(pre_render) = self.pre_render.take() {
            pre_render(self, area);
        }

        self.max_len = 0;
        self.row = std::cmp::min(self.row, area.height.saturating_sub(1) as usize);
        let style = cx.editor.theme.get(&self.tree_symbol_style);
        let folder_style = cx.editor.theme.get("special");
        let last_item_index = self.items.len().saturating_sub(1);
        let skip = self.selected.saturating_sub(self.row);
        let iter = self
            .items
            .iter()
            .skip(skip)
            .take(area.height as usize)
            .enumerate();
        for (index, elem) in iter {
            let row = index as u16;
            let mut area = Rect::new(area.x, area.y + row, area.width, 1);
            let indent = if elem.level > 0 {
                if index + skip != last_item_index {
                    format!("{}", "│ ".repeat(elem.level - 1))
                } else {
                    format!("{}", "".repeat(elem.level - 1))
                }
            } else {
                "".to_string()
            };

            let indent_len = indent.chars().count();
            if indent_len > self.col {
                let indent: String = indent.chars().skip(self.col).collect();
                if !indent.is_empty() {
                    surface.set_stringn(area.x, area.y, &indent, area.width as usize, style);
                    area = area.clip_left(indent.width() as u16);
                }
            };
            let mut start_index = self.col.saturating_sub(indent_len);
            let mut text = elem.item.text(cx, skip + index == self.selected, params);
            self.max_len = self.max_len.max(text.width() + indent.len() - 2);
            for span in text.0.iter_mut() {
                if area.width == 0 {
                    return;
                }
                if start_index == 0 {
                    let mut icon_offset = 0;
                    if let Some((icon, color)) = elem.item.icon() {
                        let style = folder_style.fg(*color);
                        surface.set_string(area.x, area.y, icon, style);
                        icon_offset = 2;
                    }
                    surface.set_span(area.x + icon_offset, area.y, span, area.width - icon_offset);
                    area = area.clip_left((span.width() - icon_offset as usize) as u16);
                } else {
                    let span_width = span.width();
                    if start_index > span_width {
                        start_index -= span_width;
                    } else {
                        let content: String = span
                            .content
                            .chars()
                            .filter(|c| {
                                if start_index > 0 {
                                    start_index = start_index.saturating_sub(c.to_string().width());
                                    false
                                } else {
                                    true
                                }
                            })
                            .collect();
                        let mut cont = String::new();
                        cont.push_str("");
                        cont.push_str(&content);
                        surface.set_string_truncated(
                            area.x,
                            area.y,
                            &cont,
                            area.width as usize,
                            |_| span.style,
                            false,
                            false,
                        );
                        start_index = 0
                    }
                }
            }
        }
    }

    pub fn handle_event(
        &mut self,
        event: Event,
        cx: &mut Context,
        params: &mut T::Params,
    ) -> EventResult {
        let key_event = match event {
            Event::Key(event) => event,
            Event::Resize(..) => return EventResult::Consumed(None),
            _ => return EventResult::Ignored(None),
        };
        if let Some(mut on_next_key) = self.on_next_key.take() {
            on_next_key(cx, self, key_event);
            return EventResult::Consumed(None);
        }
        let count = std::mem::replace(&mut self.count, 0);
        match key_event.into() {
            key!(i @ '0'..='9') => self.count = i.to_digit(10).unwrap() as usize + count * 10,
            key!('k') | shift!(Tab) | key!(Up) => self.move_up(1.max(count)),
            key!('j') | key!(Tab) | key!(Down) => self.move_down(1.max(count)),
            key!('z') => self.fold_current_level(),
            key!('h') => self.move_left(1.max(count)),
            key!('l') => self.move_right(1.max(count)),
            shift!('G') => self.move_down(usize::MAX / 2),
            key!(Enter) => self.on_enter(cx, params),
            key!(' ') => self.on_enter(cx, params),
            ctrl!('d') | ctrl!('j') => self.move_down_half_page(),
            ctrl!('u') | ctrl!('k') => self.move_up_half_page(),
            shift!('D') => self.move_down_page(),
            shift!('U') => self.move_up_page(),
            key!('g') => {
                self.on_next_key = Some(Box::new(|_, tree, event| match event.into() {
                    key!('g') => tree.move_up(usize::MAX / 2),
                    key!('e') => tree.move_down(usize::MAX / 2),
                    _ => {}
                }));
            }
            _ => return EventResult::Ignored(None),
        }

        EventResult::Consumed(None)
    }
}

impl<T: TreeItem + Clone> Tree<T> {
    pub fn filter(&mut self, s: &str, cx: &mut Context, params: &mut T::Params) {
        fn filter_recursion<T>(
            elems: &Vec<Elem<T>>,
            mut index: usize,
            s: &str,
            cx: &mut Context,
            params: &mut T::Params,
        ) -> (Vec<Elem<T>>, usize)
        where
            T: TreeItem + Clone,
        {
            let mut retain = vec![];
            let elem = &elems[index];
            loop {
                let child = match elems.get(index + 1) {
                    Some(child) if child.item.is_child(&elem.item) => child,
                    _ => break,
                };
                index += 1;
                let next = elems.get(index + 1);
                if next.map_or(false, |n| n.item.is_child(&child.item)) {
                    let (sub_retain, current_index) = filter_recursion(elems, index, s, cx, params);
                    retain.extend(sub_retain);
                    index = current_index;
                } else if child.item.filter(cx, s, params) {
                    retain.push(child.clone());
                }
            }
            if !retain.is_empty() || elem.item.filter(cx, s, params) {
                retain.insert(0, elem.clone());
            }
            (retain, index)
        }

        if s.is_empty() {
            if let Some((_, recycle)) = self.recycle.take() {
                self.items = recycle;
                self.restore_view();
                return;
            }
        }

        let mut retain = vec![];
        let mut index = 0;
        let items = match &self.recycle {
            Some((pre, _)) if pre == s => return,
            Some((pre, recycle)) if pre.contains(s) => recycle,
            _ => &self.items,
        };
        while let Some(elem) = items.get(index) {
            let next = items.get(index + 1);
            if next.map_or(false, |n| n.item.is_child(&elem.item)) {
                let (sub_items, current_index) = filter_recursion(items, index, s, cx, params);
                index = current_index;
                retain.extend(sub_items);
            } else if elem.item.filter(cx, s, params) {
                retain.push(elem.clone())
            }
            index += 1;
        }

        if retain.is_empty() {
            if let Some((_, recycle)) = self.recycle.take() {
                self.items = recycle;
                self.restore_view();
            }
            return;
        }

        let recycle = std::mem::replace(&mut self.items, retain);
        if let Some(r) = self.recycle.as_mut() {
            r.0 = s.into()
        } else {
            self.recycle = Some((s.into(), recycle));
            self.save_view();
        }

        self.selected = self
            .find(0, false, |elem| elem.item.filter(cx, s, params))
            .unwrap_or(0);
        self.row = self.selected;
    }

    pub fn clean_recycle(&mut self) {
        self.recycle = None;
    }

    pub fn restore_recycle(&mut self) {
        if let Some((_, recycle)) = self.recycle.take() {
            self.items = recycle;
        }
    }
}
