use egui::{Response, RichText, TextEdit};
use serde::{Deserialize, Serialize};

use crate::{PROJECT_FOLDER, util};

#[derive(Serialize, Deserialize)]
pub struct Tag {
    pub id: String,
    pub name: String,
    pub description: String,
    pub color: egui::Color32,

    #[serde(skip)]
    pub saved: bool,

    #[serde(skip)]
    pub error: Option<util::Error>,
}

impl Default for Tag {
    fn default() -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            name: String::new(),
            description: String::new(),
            color: egui::Color32::from_rgb(20, 20, 20),
            saved: false,
            error: None,
        }
    }
}

impl Clone for Tag {
    fn clone(&self) -> Self {
        Self {
            id: self.id.clone(),
            name: self.name.clone(),
            description: self.description.clone(),
            color: self.color,
            saved: self.saved,
            error: None,
        }
    }
}

impl Tag {
    pub fn display_ui(&mut self, ui: &mut egui::Ui) -> bool {
        let mut remove = false;

        egui::Frame::new()
            .shadow(egui::Shadow {
                offset: [2, 2],
                blur: 16,
                spread: 0,
                color: egui::Color32::from_black_alpha(180),
            })
            .stroke(egui::Stroke::new(2.0, self.color))
            .corner_radius(4.0)
            .show(ui, |ui| {
                ui.horizontal(|ui| {
                    if ui.add(egui::Button::new("").frame(false)).clicked() {
                        remove = true;
                    }
                    ui.strong(&self.name);
                });
            });

        remove
    }

    pub fn list_ui(&mut self, ui: &mut egui::Ui) -> Response {
        ui.add(
            egui::Button::new(RichText::new(self.name.clone()).strong())
                .frame(false)
                .stroke(egui::Stroke::new(2.0, self.color))
                .corner_radius(4.0),
        )
    }

    pub fn ui(&mut self, ui: &mut egui::Ui) {
        util::saved_status(ui, self.saved, &self.id, &self.name);

        if let Some(error) = &mut self.error {
            error.show(ui);
        }

        if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
            if let Err(e) = self.save() {
                self.error = Some(util::Error::new(format!("Failed to save tag: {e}")));
            }
        }

        egui::Grid::new("tag_grid")
            .striped(true)
            .num_columns(2)
            .show(ui, |ui| {
                ui.strong("Name");
                if ui
                    .add(
                        TextEdit::singleline(&mut self.name)
                            .desired_width(f32::INFINITY)
                            .frame(false),
                    )
                    .changed()
                {
                    self.saved = false;
                }
                ui.end_row();

                ui.strong("Description");
                if ui
                    .add(
                        TextEdit::singleline(&mut self.description)
                            .desired_width(f32::INFINITY)
                            .frame(false),
                    )
                    .changed()
                {
                    self.saved = false;
                }
                ui.end_row();

                ui.strong("Color");
                if ui.color_edit_button_srgba(&mut self.color).changed() {
                    self.saved = false;
                }
                ui.end_row();
            });
    }

    pub fn selector_ui(tag_ids: &mut Vec<String>, ui: &mut egui::Ui, saved: Option<&mut bool>) {
        // remove duplicate tag ids
        tag_ids.sort();
        tag_ids.dedup();

        let mut remove: Vec<usize> = Vec::new();
        let mut modified = false;
        let id = ui.make_persistent_id("new_tag");
        let available_tags = Self::load_all();

        ui.horizontal(|ui| {
            let ctx = ui.ctx();
            let mut tag_selection: usize =
                ctx.memory_mut(|mem| *mem.data.get_temp_mut_or_default::<usize>(id));

            if available_tags.is_empty() {
                ui.label("No tags available");
            } else {
                egui::ComboBox::from_id_salt(id)
                    .selected_text(&available_tags[tag_selection].name)
                    .show_ui(ui, |ui| {
                        for (i, tag) in available_tags.iter().enumerate() {
                            if ui
                                .add(
                                    egui::Button::new(RichText::new(tag.name.clone()).strong())
                                        .frame(false)
                                        .stroke(egui::Stroke::new(2.0, tag.color))
                                        .corner_radius(4.0),
                                )
                                .clicked()
                            {
                                tag_selection = i;
                            }
                        }
                    });
            }

            if ui.button("Add").clicked() && tag_selection < available_tags.len() {
                tag_ids.push(available_tags[tag_selection].id.clone());
                tag_selection = 0;
                modified = true;
            }

            let ctx = ui.ctx();
            ctx.memory_mut(|mem| {
                *mem.data.get_temp_mut_or_default::<usize>(id) = tag_selection;
            });

            for (i, tag_id) in tag_ids.iter().enumerate() {
                if let Ok(mut tag) = Self::load(tag_id) {
                    if tag.display_ui(ui) {
                        remove.push(i)
                    }
                } else {
                    // if the tag doesn't exist (AKA it's been deleted)
                    remove.push(i)
                }
            }

            if !remove.is_empty() {
                modified = true;
                for i in remove {
                    tag_ids.remove(i);
                }
            }

            if let Some(saved) = saved {
                if modified {
                    *saved = false;
                }
            }
        });
    }

    pub fn load(id: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let path = PROJECT_FOLDER.join("tags").join(format!("{id}.json"));
        Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
    }

    pub fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        if self.name.is_empty() {
            self.error = Some(util::Error::new("Tag name cannot be empty".to_string()));
            return Ok(());
        }

        self.error = None;

        let path = PROJECT_FOLDER
            .join("tags")
            .join(format!("{}.json", &self.id));
        let content = serde_json::to_string_pretty(self)?;
        std::fs::write(path, content)?;
        self.saved = true;
        Ok(())
    }

    pub fn load_all() -> Vec<Self> {
        let mut tags = Vec::new();

        // scan tags folder. load tag json files
        let tags_folder = PROJECT_FOLDER.join("tags");
        if tags_folder.exists() {
            for entry in std::fs::read_dir(tags_folder).unwrap() {
                let path = entry.unwrap().path();
                if path.is_file() && path.extension().unwrap() == "json" {
                    let tag =
                        serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap();
                    tags.push(tag);
                }
            }
        }
        tags
    }
}
