use parley::FontWeight as ParleyFontWeight;
use serde::{Deserialize, Serialize};
use swash::FontRef;

pub(crate) const SANS_DATA: &[u8] = include_bytes!("../../../assets/DejaVuSans.ttf");
pub(crate) const MONO_DATA: &[u8] = include_bytes!("../../../assets/DejaVuSansMono.ttf");

pub(crate) const SANS_FAMILY: &str = "DejaVu Sans";
pub(crate) const MONO_FAMILY: &str = "DejaVu Sans Mono";

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FontFace {
    Sans,
    Mono,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FontWeight {
    Regular,
    Medium,
    Semibold,
    Bold,
}

#[must_use]
pub fn load_font(face: FontFace) -> FontRef<'static> {
    let bytes = data_for(face);
    let label = family_for(face);
    let Some(font) = FontRef::from_index(bytes, 0) else {
        panic!("bundled font {label} failed to parse; binary asset is broken");
    };
    font
}

pub(crate) const fn data_for(face: FontFace) -> &'static [u8] {
    match face {
        FontFace::Sans => SANS_DATA,
        FontFace::Mono => MONO_DATA,
    }
}

pub(crate) const fn family_for(face: FontFace) -> &'static str {
    match face {
        FontFace::Sans => SANS_FAMILY,
        FontFace::Mono => MONO_FAMILY,
    }
}

pub(crate) const fn parley_weight(weight: FontWeight) -> ParleyFontWeight {
    match weight {
        FontWeight::Regular => ParleyFontWeight::NORMAL,
        FontWeight::Medium => ParleyFontWeight::MEDIUM,
        FontWeight::Semibold => ParleyFontWeight::SEMI_BOLD,
        FontWeight::Bold => ParleyFontWeight::BOLD,
    }
}

#[cfg(test)]
mod tests {
    use super::{
        FontFace, FontWeight, MONO_DATA, MONO_FAMILY, SANS_DATA, SANS_FAMILY, data_for, family_for,
        load_font, parley_weight,
    };
    use parley::FontWeight as ParleyFontWeight;

    #[test]
    fn sans_and_mono_data_are_distinct_bundled_assets() {
        assert!(!SANS_DATA.is_empty());
        assert!(!MONO_DATA.is_empty());
        assert_ne!(SANS_DATA, MONO_DATA);
    }

    #[test]
    fn data_for_resolves_face_to_its_asset() {
        assert_eq!(data_for(FontFace::Sans), SANS_DATA);
        assert_eq!(data_for(FontFace::Mono), MONO_DATA);
    }

    #[test]
    fn family_for_resolves_face_to_its_family_name() {
        assert_eq!(family_for(FontFace::Sans), SANS_FAMILY);
        assert_eq!(family_for(FontFace::Mono), MONO_FAMILY);
    }

    #[test]
    fn load_font_returns_valid_fontref_for_each_face() {
        let sans = load_font(FontFace::Sans);
        let mono = load_font(FontFace::Mono);
        assert!(sans.charmap().map(u32::from('A')) > 0);
        assert!(mono.charmap().map(u32::from('A')) > 0);
    }

    #[test]
    fn bundled_fonts_cover_arabic_baseline_for_complex_script_dim_labels() {
        let sans = load_font(FontFace::Sans);
        let mono = load_font(FontFace::Mono);
        ['\u{0627}', '\u{0644}', '\u{0637}', '\u{0648}', '\u{0645}']
            .into_iter()
            .for_each(|ch| {
                let cp = u32::from(ch);
                assert!(
                    sans.charmap().map(cp) > 0,
                    "sans must cover arabic codepoint U+{cp:04X}",
                );
                assert!(
                    mono.charmap().map(cp) > 0,
                    "mono must cover arabic codepoint U+{cp:04X}",
                );
            });
    }

    #[test]
    fn parley_weight_round_trips_each_step() {
        assert_eq!(parley_weight(FontWeight::Regular), ParleyFontWeight::NORMAL);
        assert_eq!(parley_weight(FontWeight::Medium), ParleyFontWeight::MEDIUM);
        assert_eq!(
            parley_weight(FontWeight::Semibold),
            ParleyFontWeight::SEMI_BOLD
        );
        assert_eq!(parley_weight(FontWeight::Bold), ParleyFontWeight::BOLD);
    }
}
