// Build a tree of folders (not files)
// Folders can be expanded and collapsed
// Folders can by selected
// Folders will fetch their children when expanded

use std::{default, fmt::format, path::PathBuf};

use ratatui::{
    crossterm::event::{Event, KeyCode, KeyEvent},
    style::{Color, Style},
    text::{Line, Span},
    widgets::{Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Widget},
    Frame,
};

use super::Component;

// use eframe::egui;

pub struct Tree {
    root: Folder,
    // pub selected: PathBuf,
    vertical_scroll: usize,
    vertical_scroll_state: ScrollbarState,
    selected_idx: usize,
}

impl Component for Tree {
    fn render(
        &mut self,
        f: &mut ratatui::Frame,
        rect: ratatui::prelude::Rect,
        mut evt: super::AppEventClient,
    ) {
        if evt.has_focus() {
            if let Some(evt) = &evt.event {
                if let Event::Key(key) = evt {
                    match key.code {
                        KeyCode::Up => {
                            if self.selected_idx > 0 {
                                self.selected_idx -= 1;
                            }
                        }
                        KeyCode::Down => {
                            let entries = self.root.get_entries();
                            if self.selected_idx < entries.len() - 1 {
                                self.selected_idx += 1;
                            }
                        }
                        _ => {}
                    }
                }
            }

            evt.register_helper("->", "Expand");
            evt.register_helper("<-", "Collapse");
            evt.register_helper("↑", "Up");
            evt.register_helper("↓", "Down");
        }

        let block = Block::bordered()
            .title("Select Folder")
            .borders(ratatui::widgets::Borders::ALL)
            .border_style(ratatui::style::Style::default().fg(ratatui::style::Color::White));

        let inner_area = block.inner(rect);
        f.render_widget(block, rect);

        let mut tmp = self.selected_idx;
        self.root.apply_event(&evt, &mut tmp);
        let entries = self.root.get_entries();
        let lines: Vec<Line> = entries.iter().map(|entry| entry.get_line()).collect();

        if self.selected_idx >= self.vertical_scroll + inner_area.height as usize {
            self.vertical_scroll = self.selected_idx - inner_area.height as usize + 1;
        } else if self.selected_idx < self.vertical_scroll as usize {
            self.vertical_scroll = self.selected_idx;
        }

        self.vertical_scroll_state = self
            .vertical_scroll_state
            .content_length(lines.len())
            .position(self.vertical_scroll as usize);

        let paragraph = Paragraph::new(lines).scroll((self.vertical_scroll as u16, 0));

        f.render_widget(paragraph, inner_area);
        f.render_stateful_widget(
            Scrollbar::new(ScrollbarOrientation::VerticalRight)
                .begin_symbol(Some("↑"))
                .end_symbol(Some("↓")),
            inner_area,
            &mut self.vertical_scroll_state,
        );

        // todo!("Implement the Widget trait for Tree");
    }
}

impl Tree {
    pub fn new(initial_path: impl Into<PathBuf>) -> Self {
        let path: PathBuf = initial_path.into();
        let name = path.to_string_lossy().to_string();

        let mut root = Folder::new(name, path, 0);
        root.selected = true;

        Self {
            root,
            // selected: PathBuf::new(),
            vertical_scroll: 0,
            vertical_scroll_state: ScrollbarState::default(),
            selected_idx: 0,
        }
    }

    pub fn get_selected(&self) -> PathBuf {
        let entries = self.root.get_entries();
        if self.selected_idx < entries.len() {
            return entries[self.selected_idx].path.clone();
        }
        PathBuf::new()
    }

    // pub fn show(&mut self, ui: &mut egui::Ui) {
    //     ui.vertical(|ui| {
    //         self.root.show(ui, &mut self.selected);
    //     });
    // }
}

pub struct Folder {
    name: String,
    path: PathBuf,
    expanded: bool,
    children: Option<Vec<Folder>>,
    selected: bool,
    depth: usize,
}

impl Folder {
    pub fn new(name: impl Into<String>, path: impl Into<PathBuf>, depth: usize) -> Self {
        Self {
            name: name.into(),
            path: path.into(),
            expanded: false,
            selected: false,
            children: Default::default(),
            depth,
        }
    }

    pub fn expand(&mut self) {
        if self.children.is_none() {
            let mut children_data = vec![];
            // println!("Reading dir: {:?}", self.path);
            std::fs::read_dir(&self.path)
                .map(|entries| {
                    for entry in entries.flatten() {
                        let path = entry.path();
                        if path.is_dir() {
                            // println!(
                            //     "Found dir: {:?}, file {}, link {}",
                            //     path,
                            //     path.is_file(),
                            //     path.is_symlink()
                            // );
                            let path2 = path.clone();
                            let name = path2.file_name().unwrap().to_string_lossy();
                            children_data.push(Folder::new(name, path, self.depth + 1));
                        }
                    }
                })
                .unwrap_or_else(|err| {
                    eprintln!("Failed to read dir: {}", err);
                });
            children_data.sort_by_key(|folder| folder.name.clone());
            self.children.replace(children_data);
        }
    }

    pub fn apply_event(&mut self, evt: &super::AppEventClient, selected_idx: &mut usize) {
        if *selected_idx == 0 {
            self.selected = true;
            *selected_idx = usize::MAX;
        } else {
            self.selected = false;
        }
        *selected_idx -= 1;
        if evt.has_focus() && self.selected {
            if let Some(evt) = &evt.event {
                if let Event::Key(key) = evt {
                    match key.code {
                        KeyCode::Right | KeyCode::Enter => {
                            self.expand();
                            self.expanded = true;
                        }
                        KeyCode::Left => {
                            self.expanded = false;
                        }
                        _ => {}
                    }
                }
            }
        }

        if self.expanded {
            if let Some(children) = &mut self.children {
                for child in children {
                    child.apply_event(evt, selected_idx);
                    // if child.selected {
                    //     self.selected = true;
                    // }
                }
            }
        }
    }

    pub fn get_entries(&self) -> Vec<&Folder> {
        let mut entries: Vec<&Folder> = vec![];
        if self.expanded {
            if let Some(children) = &self.children {
                for child in children {
                    entries.extend(child.get_entries());
                }
            }
        }

        let mut res: Vec<&Folder> = vec![self];
        res.extend(entries);

        res
    }

    pub fn get_line(&self) -> Line {
        let pad = " ".repeat(self.depth);
        Line::from(vec![Span::styled(
            format!(
                "{}{} {}",
                pad,
                if self.expanded { "▼" } else { "▶" },
                self.name
            ),
            if self.selected {
                Style::new().bg(Color::White).fg(Color::Black)
            } else {
                Style::default()
            },
        )])
    }

    // pub fn show(&mut self, ui: &mut egui::Ui, selected: &mut PathBuf) {
    //     let Self {
    //         name,
    //         path,
    //         expanded,
    //         children,
    //     } = self;

    //     if children.is_none() {
    //         let mut children_data = vec![];
    //         println!("Reading dir: {:?}", path);
    //         std::fs::read_dir(&path)
    //             .map(|entries| {
    //                 for entry in entries.flatten() {
    //                     let path = entry.path();
    //                     if path.is_dir() {
    //                         println!(
    //                             "Found dir: {:?}, file {}, link {}",
    //                             path,
    //                             path.is_file(),
    //                             path.is_symlink()
    //                         );
    //                         let path2 = path.clone();
    //                         let name = path2.file_name().unwrap().to_string_lossy();
    //                         children_data.push(Folder::new(name, path));
    //                     }
    //                 }
    //             })
    //             .unwrap_or_else(|err| {
    //                 eprintln!("Failed to read dir: {}", err);
    //             });
    //         children_data.sort_by_key(|folder| folder.name.clone());
    //         children.replace(children_data);
    //     }

    //     ui.horizontal(|ui| {
    //         if let Some(children) = children {
    //             if !children.is_empty() {
    //                 let label = if *expanded { "▼" } else { "▶" };
    //                 let response = ui.selectable_label(false, label);

    //                 if response.clicked() && !children.is_empty() {
    //                     *expanded = !*expanded;
    //                 }
    //             }
    //         } else {
    //             ui.label("⌛");
    //         }

    //         if ui
    //             .selectable_label(*selected == *path, name.to_owned())
    //             .clicked()
    //         {
    //             *selected = path.clone();
    //         }
    //     });

    //     if let Some(children) = children {
    //         if *expanded {
    //             ui.indent("", |ui| {
    //                 for child in children {
    //                     child.show(ui, selected);
    //                 }
    //             });
    //         }
    //     }
    // }
}
