//! Small pure validation helpers shared across value objects.

use crate::error::DomainError;

/// Returns the trimmed string if non-empty, otherwise [`DomainError::EmptyField`].
pub(crate) fn non_empty(value: &str, field: &'static str) -> Result<(), DomainError> {
    if value.trim().is_empty() {
        Err(DomainError::EmptyField { field })
    } else {
        Ok(())
    }
}

/// Validates that `var` is a syntactically valid environment-variable identifier:
/// non-empty, starts with a letter or `_`, and contains only ASCII alphanumeric
/// characters or `_`.
pub(crate) fn valid_env_var(var: &str) -> Result<(), DomainError> {
    let invalid = || DomainError::InvalidEnvVar {
        value: var.to_string(),
    };
    let mut chars = var.chars();
    match chars.next() {
        Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
        _ => return Err(invalid()),
    }
    if chars.all(|c| c.is_ascii_alphanumeric() || c == '_') {
        Ok(())
    } else {
        Err(invalid())
    }
}

/// Validates that a path is relative and does not escape its root via `..`.
///
/// Used for [`crate::profile::ContextInjection::ConventionFile`] targets and
/// manifest `.md` paths, which must stay inside the project / `.ideai/` tree.
pub(crate) fn relative_safe(path: &str) -> Result<(), DomainError> {
    let err = || DomainError::PathNotRelativeSafe {
        path: path.to_string(),
    };
    if path.is_empty() {
        return Err(err());
    }
    // Reject absolute POSIX paths and Windows drive / UNC paths.
    let bytes = path.as_bytes();
    if bytes[0] == b'/' || bytes[0] == b'\\' {
        return Err(err());
    }
    if path.len() >= 2 && bytes[1] == b':' && bytes[0].is_ascii_alphabetic() {
        return Err(err());
    }
    // Reject any `..` traversal component (handle both separators).
    for component in path.split(['/', '\\']) {
        if component == ".." {
            return Err(err());
        }
    }
    Ok(())
}
