use std::borrow::Borrow;
use derive_more::Display;
use lazy_static::lazy_static;
use regex::Regex;
use smol_str::SmolStr;
use thiserror::Error;
pub static PATH_COMPONENT_REGEX_STR: &str = r"[\w--\d]\w*";
lazy_static! {
    pub static ref PATH_REGEX: Regex =
        Regex::new(&format!(r"^{0}(\.{0})*$", PATH_COMPONENT_REGEX_STR)).unwrap();
}
#[derive(
    Clone,
    Debug,
    Display,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    serde::Serialize,
    serde::Deserialize,
)]
pub struct IdentList(SmolStr);
impl IdentList {
    pub fn new(n: impl Into<SmolStr>) -> Result<Self, InvalidIdentifier> {
        let n = n.into();
        if PATH_REGEX.is_match(n.as_str()) {
            Ok(IdentList(n))
        } else {
            Err(InvalidIdentifier(n))
        }
    }
    pub fn split_last(&self) -> Option<(IdentList, SmolStr)> {
        let (prefix, suffix) = self.0.rsplit_once('.')?;
        let prefix = Self::new_unchecked(prefix);
        let suffix = suffix.into();
        Some((prefix, suffix))
    }
    pub const fn new_unchecked(n: &str) -> Self {
        IdentList(SmolStr::new_inline(n))
    }
}
impl Borrow<str> for IdentList {
    fn borrow(&self) -> &str {
        self.0.borrow()
    }
}
impl std::ops::Deref for IdentList {
    type Target = str;
    fn deref(&self) -> &str {
        self.0.deref()
    }
}
impl TryInto<IdentList> for &str {
    type Error = InvalidIdentifier;
    fn try_into(self) -> Result<IdentList, InvalidIdentifier> {
        IdentList::new(SmolStr::new(self))
    }
}
#[derive(Clone, Debug, PartialEq, Eq, Error)]
#[error("Invalid identifier {0}")]
pub struct InvalidIdentifier(SmolStr);
#[cfg(test)]
mod test {
    mod proptest {
        use crate::hugr::ident::IdentList;
        use ::proptest::prelude::*;
        impl Arbitrary for super::IdentList {
            type Parameters = ();
            type Strategy = BoxedStrategy<Self>;
            fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
                use crate::proptest::any_ident_string;
                use proptest::collection::vec;
                vec(any_ident_string(), 1..2)
                    .prop_map(|vs| {
                        IdentList::new(
                            itertools::intersperse(
                                vs.into_iter().map(Into::<String>::into),
                                ".".into(),
                            )
                            .collect::<String>(),
                        )
                        .unwrap()
                    })
                    .boxed()
            }
        }
        proptest! {
            #[test]
            fn arbitrary_identlist_valid((IdentList(ident_list)): IdentList) {
                assert!(IdentList::new(ident_list).is_ok())
            }
        }
    }
    use super::IdentList;
    #[test]
    fn test_idents() {
        IdentList::new("foo").unwrap();
        IdentList::new("_foo").unwrap();
        IdentList::new("Bar_xyz67").unwrap();
        IdentList::new("foo.bar").unwrap();
        IdentList::new("foo.bar.baz").unwrap();
        IdentList::new("42").unwrap_err();
        IdentList::new("foo.42").unwrap_err();
        IdentList::new("xyz-5").unwrap_err();
        IdentList::new("foo..bar").unwrap_err();
        IdentList::new(".foo").unwrap_err();
    }
}