use nalgebra_glm::{Vec2, Vec4};
use std::collections::HashMap;

#[derive(Clone, Debug)]
pub struct GlyphInfo {
    pub uv_rect: Vec4,
    pub size: Vec2,
    pub bearing: Vec2,
    pub advance: f32,
}

pub struct FontAtlasData {
    pub texture_index: u32,
    pub glyphs: HashMap<char, GlyphInfo>,
    pub width: u32,
    pub height: u32,
    pub font_size: f32,
    pub sdf_range: f32,
}

#[derive(Default)]
pub struct FontManager {
    pub fonts: Vec<FontAtlasData>,
    pub default_font: usize,
}

impl FontManager {
    pub fn new() -> Self {
        Self {
            fonts: Vec::new(),
            default_font: 0,
        }
    }

    pub fn add_font(&mut self, atlas: FontAtlasData) -> usize {
        let index = self.fonts.len();
        self.fonts.push(atlas);
        index
    }

    pub fn get_font(&self, index: usize) -> Option<&FontAtlasData> {
        self.fonts.get(index)
    }

    pub fn get_default_font(&self) -> Option<&FontAtlasData> {
        self.fonts.get(self.default_font)
    }

    pub fn font_count(&self) -> usize {
        self.fonts.len()
    }
}

#[derive(Default)]
pub struct TextCache {
    pub font_manager: FontManager,
    pub text_strings: Vec<Option<String>>,
    pub next_free_slot: usize,
}

impl TextCache {
    pub fn new() -> Self {
        Self {
            font_manager: FontManager::new(),
            text_strings: Vec::new(),
            next_free_slot: 0,
        }
    }

    pub fn add_text(&mut self, content: impl Into<String>) -> usize {
        let content = content.into();

        if self.next_free_slot < self.text_strings.len() {
            let slot = self.next_free_slot;
            self.text_strings[slot] = Some(content);
            self.next_free_slot = self.find_next_free_slot();
            slot
        } else {
            let slot = self.text_strings.len();
            self.text_strings.push(Some(content));
            self.next_free_slot = slot + 1;
            slot
        }
    }

    pub fn get_text(&self, index: usize) -> Option<&str> {
        self.text_strings.get(index).and_then(|opt| opt.as_deref())
    }

    pub fn set_text(&mut self, index: usize, content: impl Into<String>) -> bool {
        if let Some(text_opt) = self.text_strings.get_mut(index) {
            *text_opt = Some(content.into());
            true
        } else {
            false
        }
    }

    pub fn remove_text(&mut self, index: usize) -> bool {
        if let Some(text_opt) = self.text_strings.get_mut(index) {
            if text_opt.is_some() {
                *text_opt = None;
                if index < self.next_free_slot {
                    self.next_free_slot = index;
                }
                true
            } else {
                false
            }
        } else {
            false
        }
    }

    pub fn is_slot_valid(&self, index: usize) -> bool {
        self.text_strings
            .get(index)
            .is_some_and(|opt| opt.is_some())
    }

    pub fn get_all_text_with_indices(&self) -> Vec<(usize, &str)> {
        self.text_strings
            .iter()
            .enumerate()
            .filter_map(|(i, opt)| opt.as_deref().map(|s| (i, s)))
            .collect()
    }

    pub fn get_valid_slots(&self) -> Vec<usize> {
        self.text_strings
            .iter()
            .enumerate()
            .filter_map(|(i, opt)| if opt.is_some() { Some(i) } else { None })
            .collect()
    }

    pub fn compact(&mut self) -> Vec<(usize, usize)> {
        let mut new_strings = Vec::new();
        let mut index_mapping = Vec::new();

        for (old_index, opt) in self.text_strings.iter().enumerate() {
            if let Some(text) = opt {
                index_mapping.push((old_index, new_strings.len()));
                new_strings.push(Some(text.clone()));
            }
        }

        self.text_strings = new_strings;
        self.next_free_slot = self.text_strings.len();

        index_mapping
    }

    fn find_next_free_slot(&self) -> usize {
        for (i, opt) in self.text_strings.iter().enumerate() {
            if opt.is_none() {
                return i;
            }
        }
        self.text_strings.len()
    }
}
