use walkdir::{DirEntry, WalkDir};

use crate::{
    PROJECT_FOLDER, RightPanelContent,
    content_editor::MainEditor,
    editors::{
        asset_editor::Asset, content_editor::ContentSection, object_editor::ObjectInstance,
        tags::Tag, template_editor::Template,
    },
    note_editor::Note,
};

pub struct Explorer {
    templates: Vec<Template>,
    objects: Vec<ObjectInstance>,
    notes: Vec<Note>,
    documents: Vec<MainEditor>,
    tags: Vec<Tag>,
}

impl Explorer {
    pub fn new() -> Self {
        Self {
            templates: Vec::new(),
            objects: Vec::new(),
            notes: Vec::new(),
            documents: Vec::new(),
            tags: Vec::new(),
        }
    }

    pub fn objects(&self) -> Vec<ObjectInstance> {
        self.objects.clone()
    }

    pub fn ui(
        &mut self,
        to_load: &mut Option<RightPanelContent>,
        load_doc: &mut Option<MainEditor>,
        ui: &mut egui::Ui,
    ) {
        self.load_templates().expect("Failed to load templates");
        self.load_objects().expect("Failed to load objects");
        self.load_notes().expect("Failed to load notes");
        self.load_documents().expect("Failed to load documents");
        self.load_tags().expect("Failed to load tags");

        ui.vertical(|ui| {
            self.render_templates(ui, to_load);
            self.render_notes(ui, to_load);
            self.render_doc_root(ui, load_doc);
            self.render_tags(ui, to_load);
            self.render_assets(ui, to_load);
        });
    }

    fn render_templates(&mut self, ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>) {
        egui::collapsing_header::CollapsingState::load_with_default_open(
            ui.ctx(),
            ui.make_persistent_id("templates"),
            true,
        )
        .show_header(ui, |ui| {
            ui.horizontal(|ui| {
                ui.label("Templates");
                if ui.button("+").clicked() {
                    *to_load = Some(RightPanelContent::template(Some(Template::default())));
                }
            });
        })
        .body(|ui| {
            for template in &self.templates {
                let id = ui.make_persistent_id(template.name.clone());
                egui::collapsing_header::CollapsingState::load_with_default_open(
                    ui.ctx(),
                    id,
                    true,
                )
                .show_header(ui, |ui| {
                    // load the template
                    if ui.selectable_label(false, template.name.clone()).clicked() {
                        *to_load = Some(RightPanelContent::template(Some(template.clone())));
                    }

                    // create a new object based on this template
                    if ui.button("+").clicked() {
                        *to_load = Some(RightPanelContent::object(Some(ObjectInstance::new(
                            template,
                        ))));
                    }
                })
                .body(|ui| {
                    for object in &self.objects {
                        if object.template_id == template.id {
                            ui.horizontal(|ui| {
                                ui.add_space(10.0);

                                // load the object
                                if ui.selectable_label(false, &object.name).clicked() {
                                    *to_load =
                                        Some(RightPanelContent::object(Some(object.clone())));
                                }
                            });
                        }
                    }
                });
            }
        });
    }

    fn render_notes(&mut self, ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>) {
        egui::collapsing_header::CollapsingState::load_with_default_open(
            ui.ctx(),
            ui.make_persistent_id("notes"),
            true,
        )
        .show_header(ui, |ui| {
            ui.horizontal(|ui| {
                ui.label("Notes");
                if ui.button("+").clicked() {
                    *to_load = Some(RightPanelContent::note(Some(Note::default())));
                }
            });
        })
        .body(|ui| {
            for note in &self.notes {
                ui.horizontal(|ui| {
                    ui.add_space(10.0);

                    // load the note
                    if ui.selectable_label(false, &note.name).clicked() {
                        *to_load = Some(RightPanelContent::note(Some(note.clone())));
                    }
                });
            }
        });
    }

    fn render_doc_root(&self, ui: &mut egui::Ui, load_doc: &mut Option<MainEditor>) {
        egui::collapsing_header::CollapsingState::load_with_default_open(
            ui.ctx(),
            ui.make_persistent_id("projects"),
            true,
        )
        .show_header(ui, |ui| {
            ui.horizontal(|ui| {
                ui.label("Projects");
                if ui.button("+").clicked() {
                    *load_doc = Some(MainEditor::open(ContentSection::new()));
                }
            });
        })
        .body(|ui| {
            // Convert MainEditor vec to ContentSection vec
            let content_sections: Vec<ContentSection> = self
                .documents
                .iter()
                .map(|doc| doc.content.clone())
                .collect();

            Self::render_doc_branch(ui, &content_sections, None, load_doc);
        });
    }

    /// Recursively renders a tree of documents.
    ///
    /// Each document is represented by a single element in the `documents` array.
    /// The `parent_id` parameter is used to filter out documents that do not have the current
    /// parent. If `parent_id` is `None`, all documents are rendered.
    ///
    /// `load_doc` is a mutable reference to a `MainEditor`. When a document is clicked, it
    /// is loaded into the `MainEditor` and returned as `Some`.
    fn render_doc_branch(
        ui: &mut egui::Ui,
        documents: &[ContentSection],
        parent_id: Option<&str>,
        load_doc: &mut Option<MainEditor>,
    ) {
        // Filter documents that have the current parent (or no parent if this is the root)
        let child_docs: Vec<&ContentSection> = documents
            .iter()
            .filter(|doc| doc.parent.as_deref() == parent_id)
            .collect();

        for doc in child_docs {
            egui::collapsing_header::CollapsingState::load_with_default_open(
                ui.ctx(),
                ui.make_persistent_id(&doc.id),
                false,
            )
            .show_header(ui, |ui| {
                ui.horizontal(|ui| {
                    // Document title
                    if ui.selectable_label(false, &doc.title).clicked() {
                        *load_doc = Some(MainEditor::open(doc.clone()));
                    }

                    // Add child button
                    if ui.button("+").clicked() {
                        let child = doc.create_child();
                        *load_doc = Some(MainEditor::open(child));
                    }
                });
            })
            .body(|ui| {
                // recursive call to render the next level of documents
                Self::render_doc_branch(ui, documents, Some(&doc.id), load_doc);
            });
        }
    }

    fn render_tags(&mut self, ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>) {
        egui::collapsing_header::CollapsingState::load_with_default_open(
            ui.ctx(),
            ui.make_persistent_id("tags"),
            true,
        )
        .show_header(ui, |ui| {
            ui.horizontal(|ui| {
                ui.label("Tags");
                if ui.button("+").clicked() {
                    *to_load = Some(RightPanelContent::Tag(Tag::default()));
                }
            });
        })
        .body(|ui| {
            for tag in &mut self.tags {
                ui.horizontal(|ui| {
                    ui.add_space(10.0);

                    // load the tag
                    if tag.list_ui(ui).clicked() {
                        *to_load = Some(RightPanelContent::Tag(tag.clone()));
                    }
                });
            }
        });
    }

    fn render_assets(&mut self, ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>) {
        egui::collapsing_header::CollapsingState::load_with_default_open(
            ui.ctx(),
            ui.make_persistent_id("assets"),
            true,
        )
        .show_header(ui, |ui| {
            ui.horizontal(|ui| {
                ui.label("Assets");
            });
        })
        .body(|ui| {
            let entries: Vec<_> = WalkDir::new(PROJECT_FOLDER.join("assets"))
                .min_depth(1)
                .max_depth(1) // Only immediate children
                .sort_by(|a, b| {
                    // Directories first, then files
                    let a_is_dir = a.file_type().is_dir();
                    let b_is_dir = b.file_type().is_dir();
                    if a_is_dir == b_is_dir {
                        a.file_name().cmp(b.file_name())
                    } else if a_is_dir {
                        std::cmp::Ordering::Less
                    } else {
                        std::cmp::Ordering::Greater
                    }
                })
                .into_iter()
                .filter_map(Result::ok)
                .collect();

            for entry in entries {
                Self::render_entry(ui, to_load, &entry);
            }
        });
    }

    fn render_entry(ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>, entry: &DirEntry) {
        let file_type = entry.file_type();
        let is_dir = file_type.is_dir();
        let file_name = entry.file_name().to_string_lossy();
        let path = entry.path();

        if is_dir {
            let entries: Vec<_> = WalkDir::new(path)
                .min_depth(1)
                .max_depth(1)
                .sort_by(|a, b| a.file_name().cmp(b.file_name()))
                .into_iter()
                .filter_map(Result::ok)
                .collect();

            egui::collapsing_header::CollapsingState::load_with_default_open(
                ui.ctx(),
                ui.make_persistent_id(&file_name),
                false,
            )
            .show_header(ui, |ui| {
                ui.horizontal(|ui| {
                    ui.label(file_name);
                    let _clicked = ui.button("+").on_hover_text("Add new item").clicked();
                });
            })
            .body(|ui| {
                // recursive call to render the next level of documents
                for entry in entries {
                    Self::render_entry(ui, to_load, &entry);
                }
            });
        } else {
            // Handle file
            if ui
                .selectable_label(false, format!("📄 {file_name}"))
                .clicked()
            {
                // use asset::load to get the file at the path
                let asset_path = path.strip_prefix(PROJECT_FOLDER.join("assets")).unwrap();
                let asset = Asset::open(asset_path.to_string_lossy().to_string());
                *to_load = Some(RightPanelContent::Asset(Box::new(asset)));
            }
        }
    }

    // load templates from the templates folder
    fn load_templates(&mut self) -> std::io::Result<()> {
        let templates_folder = PROJECT_FOLDER.join("templates");
        if !templates_folder.exists() {
            std::fs::create_dir_all(&templates_folder)?;
        }
        let mut templates = Vec::new();
        for entry in std::fs::read_dir(&templates_folder).unwrap() {
            let path = entry.unwrap().path();
            match Template::load(path.file_stem().unwrap().to_str().unwrap()) {
                Ok(t) => templates.push(t),
                Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
            }
        }
        self.templates = templates;

        Ok(())
    }

    // load objects from the objects folder
    fn load_objects(&mut self) -> std::io::Result<()> {
        let objects_folder = PROJECT_FOLDER.join("objects");
        if !objects_folder.exists() {
            std::fs::create_dir_all(&objects_folder)?;
        }
        let mut objects = Vec::new();
        for entry in std::fs::read_dir(&objects_folder).unwrap() {
            let path = entry.unwrap().path();
            match ObjectInstance::load(path.file_stem().unwrap().to_str().unwrap()) {
                Ok(o) => objects.push(o),
                Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
            }
        }
        self.objects = objects;

        Ok(())
    }

    // load notes from the notes folder
    fn load_notes(&mut self) -> std::io::Result<()> {
        let notes_folder = PROJECT_FOLDER.join("notes");
        if !notes_folder.exists() {
            std::fs::create_dir_all(&notes_folder)?;
        }
        let mut notes = Vec::new();

        for entry in std::fs::read_dir(&notes_folder).unwrap() {
            let path = entry.unwrap().path();
            match Note::load(path.file_stem().unwrap().to_str().unwrap()) {
                Ok(note) => notes.push(note),
                Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
            }
        }

        self.notes = notes;

        Ok(())
    }

    // load documents from the documents folder
    fn load_documents(&mut self) -> std::io::Result<()> {
        let documents_folder = PROJECT_FOLDER.join("documents");
        if !documents_folder.exists() {
            std::fs::create_dir_all(&documents_folder)?;
        }
        let mut documents = Vec::new();

        for entry in std::fs::read_dir(&documents_folder).unwrap() {
            let path = entry.unwrap().path();
            match ContentSection::load(path.file_stem().unwrap().to_str().unwrap()) {
                Ok(document) => documents.push(MainEditor::open(document)),
                Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
            }
        }

        self.documents = documents;

        Ok(())
    }

    fn load_tags(&mut self) -> std::io::Result<()> {
        self.tags = Tag::load_all();
        Ok(())
    }
}
