#[cfg(feature = "ids")]
pub fn uuid_v4() -> String {
    uuid::Uuid::new_v4().to_string()
}

#[cfg(feature = "ids")]
pub fn uuid_v7() -> String {
    uuid::Uuid::now_v7().to_string()
}

#[cfg(feature = "ids")]
pub fn ulid() -> String {
    use std::time::{SystemTime, UNIX_EPOCH};
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis();
    let chars: String = (0..10)
        .map(|_| {
            let idx = rand::random::<u8>() % 36;
            let c = if idx < 10 {
                b'0' + idx
            } else {
                b'A' + idx - 10
            };
            c as char
        })
        .collect();
    format!("{:>016X}{}", now, chars)
}

#[cfg(feature = "ids")]
pub fn is_uuid(s: &str) -> bool {
    uuid::Uuid::parse_str(s).is_ok()
}

#[cfg(feature = "ids")]
pub fn is_ulid(s: &str) -> bool {
    s.len() == 26 && s.chars().all(|c| c.is_ascii_alphanumeric())
}

#[cfg(feature = "ids")]
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_uuid_v4() {
        let u = uuid_v4();
        assert_eq!(u.len(), 36);
    }

    #[test]
    fn test_uuid_v7() {
        let u = uuid_v7();
        assert_eq!(u.len(), 36);
    }

    #[test]
    fn test_ulid() {
        let u = ulid();
        assert_eq!(u.len(), 26);
    }

    #[test]
    fn test_is_uuid() {
        let u = uuid::Uuid::new_v4().to_string();
        assert!(is_uuid(&u));
        assert!(!is_uuid("not-a-uuid"));
    }

    #[test]
    fn test_is_ulid() {
        assert!(is_ulid("01ARZ3NDEKTSV4RRFFQ69G5FAV"));
        assert!(!is_ulid("invalid"));
    }
}
