// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use askama::Template;
use heck::{ToSnakeCase, ToUpperCamelCase};
use icu::properties::props::*;
use std::collections::BTreeMap;
use std::path::Path;
use std::sync::LazyLock;

const GENERATED_BY: &str = std::concat!("@generated by ", std::file!());

struct Prop {
    name: &'static str,
    short_name: &'static str,
    default: u32,
}

impl Prop {
    fn new<P: EnumeratedProperty>() -> Self {
        Self {
            name: str::from_utf8(P::NAME).unwrap(),
            short_name: str::from_utf8(P::SHORT_NAME).unwrap(),
            default: icu::collections::codepointtrie::TrieValue::to_u32(P::default()),
        }
    }

    fn variants(
        &self,
    ) -> impl Iterator<Item = (u32, (&'static str, &'static str, &[&'static str]))> {
        VARIANTS[self.short_name]
            .iter()
            .map(|(&d, &(short, long, ref aliases, _))| (d, (short, long, aliases.as_slice())))
    }

    fn is_icu4c_only_value(&self, discriminant: u32) -> bool {
        VARIANTS[self.short_name][&discriminant].3
    }

    fn is_open(&self) -> bool {
        !matches!(self.name, "General_Category")
    }

    fn transform_ident(&self, long_name: &str) -> String {
        // This produces canonical Rust enum variant identifiers. It does not lead to collisions
        // as UAX#44 guarantees uniqueness under loose matching, which ignores case and underscores.
        long_name
            .replace('_', "")
            .char_indices()
            .map(|(i, c)| if i == 0 { c.to_ascii_uppercase() } else { c })
            .collect()
    }

    fn transform_ident_ffi(&self, long_name: &str) -> String {
        self.transform_ident(long_name)
            // Unfortunately we're stuck with these non-canonical names on FFI
            .replace("Ethiopic", "Ethiopian")
            .replace("ArabicNastaliq", "Nastaliq")
            .replace("LVSyllable", "LeadingVowelSyllable")
            .replace("LVTSyllable", "LeadingVowelTrailingSyllable")
    }

    fn int_type(&self) -> &'static str {
        if VARIANTS[self.short_name].len() > 200 {
            "u16"
        } else {
            "u8"
        }
    }

    fn map_type(&self) -> &'static str {
        if self.int_type() == "u8" {
            "CodePointMapData8"
        } else {
            "CodePointMapData16"
        }
    }

    fn ffi_kind(&self) -> &'static str {
        if self.is_open() { "Struct" } else { "Enum" }
    }

    fn ffi_variant_kind(&self) -> &'static str {
        if self.is_open() {
            "AssociatedConstantInStruct"
        } else {
            "EnumVariant"
        }
    }
}

#[allow(clippy::type_complexity)]
static VARIANTS: LazyLock<BTreeMap<&str, BTreeMap<u32, (&str, &str, Vec<&str>, bool)>>> =
    LazyLock::new(|| {
        let mut discriminants = BTreeMap::<_, BTreeMap<_, _>>::new();
        let mut names = BTreeMap::<_, BTreeMap<_, _>>::new();

        for line in
            include_str!("../../../../provider/source/tests/data/rscd/ucd/PropertyValueAliases.txt")
                .lines()
        {
            let line = line.split('#').next().unwrap().trim();
            if line.is_empty() {
                continue;
            }
            let mut parts = line.split(';').map(str::trim).peekable();
            let prop = parts.next().unwrap();
            let discriminant = parts
                .next_if(|d| d.parse::<u32>().is_ok())
                .map(|d| d.parse::<u32>().unwrap());
            let short_name = parts.next().unwrap();
            let long_name = parts.next().unwrap();
            let aliases = parts.collect::<Vec<_>>();
            if let Some(discriminant) = discriminant {
                discriminants.entry(prop).or_default().insert(
                    discriminant,
                    (short_name, long_name, aliases.clone(), false),
                );
            }
            names
                .entry(prop)
                .or_default()
                .insert(short_name, (long_name, aliases));
        }

        for line in
            include_str!("../../../../components/properties/tests/data/PropertyDiscriminants.txt")
                .lines()
        {
            let line = line.split('#').next().unwrap().trim();
            if line.is_empty() {
                continue;
            }
            let mut parts = line.split(';').map(str::trim);
            let prop = parts.next().unwrap();
            let short_name = parts.next().unwrap();
            let discriminant = parts.next().unwrap().parse::<u32>().unwrap();
            let mut is_icu4c_only_value = false;
            let (long_name, aliases) = names[prop].get(short_name).cloned().unwrap_or_else(|| {
                is_icu4c_only_value = true;
                let long_name = parts.next().expect(short_name);
                (long_name, Vec::new())
            });
            discriminants.entry(prop).or_default().insert(
                discriminant,
                (short_name, long_name, aliases, is_icu4c_only_value),
            );
        }

        discriminants
            .get_mut("sc")
            .unwrap()
            .insert(254, ("Chisoi", "Chisoi", Vec::new(), true));

        discriminants
    });

pub fn main() {
    let props = &[
        Prop::new::<BidiClass>(),
        Prop::new::<CanonicalCombiningClass>(),
        Prop::new::<EastAsianWidth>(),
        Prop::new::<GeneralCategory>(),
        Prop::new::<GraphemeClusterBreak>(),
        Prop::new::<HangulSyllableType>(),
        Prop::new::<IndicConjunctBreak>(),
        Prop::new::<IndicSyllabicCategory>(),
        Prop::new::<JoiningGroup>(),
        Prop::new::<JoiningType>(),
        Prop::new::<LineBreak>(),
        Prop::new::<NumericType>(),
        Prop::new::<Script>(),
        Prop::new::<SentenceBreak>(),
        Prop::new::<VerticalOrientation>(),
        Prop::new::<WordBreak>(),
    ];

    let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../..");

    #[derive(Template)]
    #[template(path = "properties_consts.rs.jinja")]
    struct PropertyConsts<'a> {
        props: &'a [Prop],
    }
    std::fs::write(
        root.join("components/properties/src/enum_values.rs"),
        PropertyConsts { props }.to_string(),
    )
    .unwrap();

    #[derive(Template)]
    #[template(path = "properties_enums.rs.jinja")]
    struct PropertiesEnums<'a> {
        props: &'a [Prop],
    }
    std::fs::write(
        root.join("ffi/capi/src/properties_enums.rs"),
        PropertiesEnums { props }.to_string(),
    )
    .unwrap();

    #[derive(Template)]
    #[template(path = "properties_maps.rs.jinja")]
    struct PropertiesMaps<'a> {
        props: &'a [Prop],
    }
    std::fs::write(
        root.join("ffi/capi/src/properties_maps.rs"),
        PropertiesMaps { props }.to_string(),
    )
    .unwrap();

    #[derive(Template)]
    #[template(path = "properties_names.rs.jinja")]
    struct PropertiesNames<'a> {
        props: &'a [Prop],
    }
    std::fs::write(
        root.join("ffi/capi/src/properties_names.rs"),
        PropertiesNames { props }.to_string(),
    )
    .unwrap();
}
