use crate::errors::{CliError, ErrorKind};

pub const MAX_NAME_LEN: usize = 100;

pub fn validate(name: &str) -> Result<(), CliError> {
    if name.is_empty() {
        return Err(CliError::new(
            ErrorKind::InvalidInput,
            "name must not be empty",
        ));
    }
    let len = name.chars().count();
    if len > MAX_NAME_LEN {
        return Err(CliError::new(
            ErrorKind::InvalidInput,
            format!(
                "name '{}' exceeds {MAX_NAME_LEN} characters",
                truncate(name, 32)
            ),
        ));
    }
    for ch in name.chars() {
        if !is_allowed(ch) {
            return Err(CliError::new(
                ErrorKind::InvalidInput,
                format!(
                    "name '{}' contains disallowed character {:?}; allowed: A-Z a-z 0-9 . _ -",
                    truncate(name, 32),
                    ch
                ),
            ));
        }
    }
    if name.starts_with('.') || name.starts_with('-') {
        return Err(CliError::new(
            ErrorKind::InvalidInput,
            format!(
                "name '{}' must not start with '.' or '-'",
                truncate(name, 32)
            ),
        ));
    }
    Ok(())
}

fn is_allowed(ch: char) -> bool {
    ch.is_ascii_alphanumeric() || matches!(ch, '.' | '_' | '-')
}

fn truncate(s: &str, max: usize) -> String {
    if s.chars().count() <= max {
        return s.to_string();
    }
    let mut out: String = s.chars().take(max).collect();
    out.push('…');
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn accepts_common_names() {
        assert!(validate("onym").is_ok());
        assert!(validate("my-tool").is_ok());
        assert!(validate("my_tool").is_ok());
        assert!(validate("tool.ext").is_ok());
        assert!(validate("tool42").is_ok());
    }

    #[test]
    fn rejects_empty() {
        assert_eq!(validate("").unwrap_err().kind, ErrorKind::InvalidInput);
    }

    #[test]
    fn rejects_path_separators() {
        assert!(validate("foo/bar").is_err());
        assert!(validate("foo\\bar").is_err());
    }

    #[test]
    fn rejects_whitespace_and_controls() {
        assert!(validate("foo bar").is_err());
        assert!(validate("foo\tbar").is_err());
        assert!(validate("foo\nbar").is_err());
    }

    #[test]
    fn rejects_leading_dot_or_dash() {
        assert!(validate(".hidden").is_err());
        assert!(validate("-flag").is_err());
    }

    #[test]
    fn rejects_overly_long() {
        let long = "a".repeat(MAX_NAME_LEN + 1);
        assert!(validate(&long).is_err());
    }

    #[test]
    fn rejects_unicode() {
        assert!(validate("nämé").is_err());
    }
}
